uio.c 是uio的核心文件。其入口函数如下,可以看到uio也是作为一个module被kernel初始化的.
static int __init uio_init(void)
{
return init_uio_class();
}
module_init(uio_init)
在init_uio_class中只要做了两件事情,一件是注册字符设备,初始化字符设备的major number,从前面的分析可以知道每个uio driver的kernel part都是一个字符设备。 第二件是初始化uio这个class,这样就可以看到/sys/class/uio
static int init_uio_class(void)
{
int ret;
/* This is the first time in here, set everything up properly */
ret = uio_major_init();
if (ret)
goto exit;
ret = class_register(&uio_class);
if (ret) {
printk(KERN_ERR "class_register failed for uio\n");
goto err_class_register;
}
return 0;
err_class_register:
uio_major_cleanup();
exit:
return ret;
}
第二件很简单只要是调用class_register 完成的,我们看看第一件事情是如何注册字符设备的。
static int uio_major_init(void)
{
static const char name[] = "uio";
struct cdev *cdev = NULL;
dev_t uio_dev = 0;
int result;
result = alloc_chrdev_region(&uio_dev, 0, UIO_MAX_DEVICES, name);
if (result)
goto out;
result = -ENOMEM;
cdev = cdev_alloc();
if (!cdev)
goto out_unregister;
cdev->owner = THIS_MODULE;
cdev->ops = &uio_fops;
kobject_set_name(&cdev->kobj, "%s", name);
result = cdev_add(cdev, uio_dev, UIO_MAX_DEVICES);
if (result)
goto out_put;
uio_major = MAJOR(uio_dev);
uio_cdev = cdev;
return 0;
out_put:
kobject_put(&cdev->kobj);
out_unregister:
unregister_chrdev_region(uio_dev, UIO_MAX_DEVICES);
out:
return result;
}
从uio_major_init 中可以看到就是调用字符设备的三步走方法,alloc_chrdev_region/cdev_alloc/cdev_add。最后从 uio_major = MAJOR(uio_dev);中得到major number。我们重点看看这个字符设备的ops。
static const struct file_operations uio_fops = {
.owner = THIS_MODULE,
.open = uio_open,
.release = uio_release,
.read = uio_read,
.write = uio_write,
.mmap = uio_mmap,
.poll = uio_poll,
.fasync = uio_fasync,
.llseek = noop_llseek,
};
当我们在用户态通过open函数打开/dev/uioX 的时候会调用uio_fops的open函数uio_open,在uio_open 中
static int uio_open(struct inode *inode, struct file *filep)
{
struct uio_device *idev;
struct uio_listener *listener;
int ret = 0;
// 首先更加minor number找到uio_device *idev。且这个minor number 是通过iminor 从inode->i_rdev中获得的
//static inline unsigned iminor(const struct inode *inode)
//{
// return MINOR(inode->i_rdev);
//}
mutex_lock(&minor_lock);
idev = idr_find(&uio_idr, iminor(inode));
mutex_unlock(&minor_lock);
if (!idev) {
ret = -ENODEV;
goto out;
}
if (!try_module_get(idev->owner)) {
ret = -ENODEV;
goto out;
}
//申请一个 struct uio_listener *listener; 结构图
listener = kmalloc(sizeof(*listener), GFP_KERNEL);
if (!listener) {
ret = -ENOMEM;
goto err_alloc_listener;
}
//给listener赋值
listener->dev = idev;
listener->event_count = atomic_read(&idev->event);
filep->private_data = listener;
//通过idev->info->open 调用具体uio driver 中的uio_info 中的open函数,例如前文中讲过的uioinfo->open = uio_dmem_genirq_open;
if (idev->info->open) {
ret = idev->info->open(idev->info, inode);
if (ret)
goto err_infoopen;
}
return 0;
err_infoopen:
kfree(listener);
err_alloc_listener:
module_put(idev->owner);
out:
return ret;
}
当在用户态调用read和write的时候同样会调用uio_fops的read/writer函数,这两个函数主要调用copy_to_user和copy_from_user。
同样当用户态调用mmap的时候会调用uio_fops的uio_mmap
static int uio_mmap(struct file *filep, struct vm_area_struct *vma)
{
struct uio_listener *listener = filep->private_data;
struct uio_device *idev = listener->dev;
int mi;
unsigned long requested_pages, actual_pages;
int ret = 0;
if (vma->vm_end < vma->vm_start)
return -EINVAL;
vma->vm_private_data = idev;
//根据vma找到在uio_info中的index,这个index的范围是0~5
mi = uio_find_mem_index(vma);
if (mi < 0)
return -EINVAL;
//从vma中得到需要映射的page即requested_pages,对其后得到实际需要的pages actual_pages。
requested_pages = vma_pages(vma);
actual_pages = ((idev->info->mem[mi].addr & ~PAGE_MASK)
+ idev->info->mem[mi].size + PAGE_SIZE -1) >> PAGE_SHIFT;
if (requested_pages > actual_pages)
return -EINVAL;
if (idev->info->mmap) {
ret = idev->info->mmap(idev->info, vma);
return ret;
}
// 根据uio driver映射的type 调用不同的函数,从下面可以知道映射的memtype可以分为两类UIO_MEM_PHYS和UIO_MEM_LOGICAL/UIO_MEM_VIRTUAL。如果是UIO_MEM_PHYS 表示user space拿到的是物理地址,如果是UIO_MEM_LOGICAL/UIO_MEM_VIRTUAL的话表示user space拿到的是虚拟地址.
switch (idev->info->mem[mi].memtype) {
case UIO_MEM_PHYS:
return uio_mmap_physical(vma);
case UIO_MEM_LOGICAL:
case UIO_MEM_VIRTUAL:
return uio_mmap_logical(vma);
default:
return -EINVAL;
}
}
在uio_mmap_physical和uio_mmap_logical 中最终要的就是设置vma->vm_ops = &uio_physical_vm_ops/ vma->vm_ops = &uio_logical_vm_ops;。这个vm_ops 的用法后面再分析.
static int __init uio_init(void)
{
return init_uio_class();
}
module_init(uio_init)
在init_uio_class中只要做了两件事情,一件是注册字符设备,初始化字符设备的major number,从前面的分析可以知道每个uio driver的kernel part都是一个字符设备。 第二件是初始化uio这个class,这样就可以看到/sys/class/uio
static int init_uio_class(void)
{
int ret;
/* This is the first time in here, set everything up properly */
ret = uio_major_init();
if (ret)
goto exit;
ret = class_register(&uio_class);
if (ret) {
printk(KERN_ERR "class_register failed for uio\n");
goto err_class_register;
}
return 0;
err_class_register:
uio_major_cleanup();
exit:
return ret;
}
第二件很简单只要是调用class_register 完成的,我们看看第一件事情是如何注册字符设备的。
static int uio_major_init(void)
{
static const char name[] = "uio";
struct cdev *cdev = NULL;
dev_t uio_dev = 0;
int result;
result = alloc_chrdev_region(&uio_dev, 0, UIO_MAX_DEVICES, name);
if (result)
goto out;
result = -ENOMEM;
cdev = cdev_alloc();
if (!cdev)
goto out_unregister;
cdev->owner = THIS_MODULE;
cdev->ops = &uio_fops;
kobject_set_name(&cdev->kobj, "%s", name);
result = cdev_add(cdev, uio_dev, UIO_MAX_DEVICES);
if (result)
goto out_put;
uio_major = MAJOR(uio_dev);
uio_cdev = cdev;
return 0;
out_put:
kobject_put(&cdev->kobj);
out_unregister:
unregister_chrdev_region(uio_dev, UIO_MAX_DEVICES);
out:
return result;
}
从uio_major_init 中可以看到就是调用字符设备的三步走方法,alloc_chrdev_region/cdev_alloc/cdev_add。最后从 uio_major = MAJOR(uio_dev);中得到major number。我们重点看看这个字符设备的ops。
static const struct file_operations uio_fops = {
.owner = THIS_MODULE,
.open = uio_open,
.release = uio_release,
.read = uio_read,
.write = uio_write,
.mmap = uio_mmap,
.poll = uio_poll,
.fasync = uio_fasync,
.llseek = noop_llseek,
};
当我们在用户态通过open函数打开/dev/uioX 的时候会调用uio_fops的open函数uio_open,在uio_open 中
static int uio_open(struct inode *inode, struct file *filep)
{
struct uio_device *idev;
struct uio_listener *listener;
int ret = 0;
// 首先更加minor number找到uio_device *idev。且这个minor number 是通过iminor 从inode->i_rdev中获得的
//static inline unsigned iminor(const struct inode *inode)
//{
// return MINOR(inode->i_rdev);
//}
mutex_lock(&minor_lock);
idev = idr_find(&uio_idr, iminor(inode));
mutex_unlock(&minor_lock);
if (!idev) {
ret = -ENODEV;
goto out;
}
if (!try_module_get(idev->owner)) {
ret = -ENODEV;
goto out;
}
//申请一个 struct uio_listener *listener; 结构图
listener = kmalloc(sizeof(*listener), GFP_KERNEL);
if (!listener) {
ret = -ENOMEM;
goto err_alloc_listener;
}
//给listener赋值
listener->dev = idev;
listener->event_count = atomic_read(&idev->event);
filep->private_data = listener;
//通过idev->info->open 调用具体uio driver 中的uio_info 中的open函数,例如前文中讲过的uioinfo->open = uio_dmem_genirq_open;
if (idev->info->open) {
ret = idev->info->open(idev->info, inode);
if (ret)
goto err_infoopen;
}
return 0;
err_infoopen:
kfree(listener);
err_alloc_listener:
module_put(idev->owner);
out:
return ret;
}
当在用户态调用read和write的时候同样会调用uio_fops的read/writer函数,这两个函数主要调用copy_to_user和copy_from_user。
同样当用户态调用mmap的时候会调用uio_fops的uio_mmap
static int uio_mmap(struct file *filep, struct vm_area_struct *vma)
{
struct uio_listener *listener = filep->private_data;
struct uio_device *idev = listener->dev;
int mi;
unsigned long requested_pages, actual_pages;
int ret = 0;
if (vma->vm_end < vma->vm_start)
return -EINVAL;
vma->vm_private_data = idev;
//根据vma找到在uio_info中的index,这个index的范围是0~5
mi = uio_find_mem_index(vma);
if (mi < 0)
return -EINVAL;
//从vma中得到需要映射的page即requested_pages,对其后得到实际需要的pages actual_pages。
requested_pages = vma_pages(vma);
actual_pages = ((idev->info->mem[mi].addr & ~PAGE_MASK)
+ idev->info->mem[mi].size + PAGE_SIZE -1) >> PAGE_SHIFT;
if (requested_pages > actual_pages)
return -EINVAL;
if (idev->info->mmap) {
ret = idev->info->mmap(idev->info, vma);
return ret;
}
// 根据uio driver映射的type 调用不同的函数,从下面可以知道映射的memtype可以分为两类UIO_MEM_PHYS和UIO_MEM_LOGICAL/UIO_MEM_VIRTUAL。如果是UIO_MEM_PHYS 表示user space拿到的是物理地址,如果是UIO_MEM_LOGICAL/UIO_MEM_VIRTUAL的话表示user space拿到的是虚拟地址.
switch (idev->info->mem[mi].memtype) {
case UIO_MEM_PHYS:
return uio_mmap_physical(vma);
case UIO_MEM_LOGICAL:
case UIO_MEM_VIRTUAL:
return uio_mmap_logical(vma);
default:
return -EINVAL;
}
}
在uio_mmap_physical和uio_mmap_logical 中最终要的就是设置vma->vm_ops = &uio_physical_vm_ops/ vma->vm_ops = &uio_logical_vm_ops;。这个vm_ops 的用法后面再分析.