UIO初始化
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;
}
static void uio_major_cleanup(void)
{
unregister_chrdev_region(MKDEV(uio_major, 0), UIO_MAX_DEVICES);
cdev_del(uio_cdev);
}
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;
}
注册设备
int __uio_register_device(struct module *owner,
struct device *parent,
struct uio_info *info)
{
struct uio_device *idev;
int ret = 0;
if (!parent || !info || !info->name || !info->version)
return -EINVAL;
info->uio_dev = NULL;
根据uio_info创建uio_device结构
idev = kzalloc(sizeof(*idev), GFP_KERNEL);
if (!idev) {
ret = -ENOMEM;
goto err_kzalloc;
}
设置所有的模块, 使用时需要增加引用计数 确保模块不会被卸载
idev->owner = owner;
idev->info = info;
init_waitqueue_head(&idev->wait);
atomic_set(&idev->event, 0);
从IDR中分配次设备号, 并将ui_device加入IDR
ret = uio_get_minor(idev);
if (ret)
goto err_get_minor;
根据次设备号 创建设备文件一般是uio0 uio1 uio2
idev->dev = device_create(&uio_class, parent,
MKDEV(uio_major, idev->minor), idev,
"uio%d", idev->minor);
if (IS_ERR(idev->dev)) {
printk(KERN_ERR "UIO: device register failed\n");
ret = PTR_ERR(idev->dev);
goto err_device_create;
}
设置sysfs属性,主要是IO内存和IO端口的属性文件
ret = uio_dev_add_attributes(idev);
if (ret)
goto err_uio_dev_add_attributes;
info->uio_dev = idev;
申请设备对应的中断, 处理函数主要做唤醒
if (info->irq && (info->irq != UIO_IRQ_CUSTOM)) {
ret = request_irq(info->irq, uio_interrupt,
info->irq_flags, info->name, idev);
if (ret)
goto err_request_irq;
}
return 0;
err_request_irq:
uio_dev_del_attributes(idev);
err_uio_dev_add_attributes:
device_destroy(&uio_class, MKDEV(uio_major, idev->minor));
err_device_create:
uio_free_minor(idev);
err_get_minor:
kfree(idev);
err_kzalloc:
return ret;
}
EXPORT_SYMBOL_GPL(__uio_register_device);
/**
* uio_unregister_device - unregister a industrial IO device
* @info: UIO device capabilities
*
*/
void uio_unregister_device(struct uio_info *info)
{
struct uio_device *idev;
if (!info || !info->uio_dev)
return;
idev = info->uio_dev;
uio_free_minor(idev);
if (info->irq && (info->irq != UIO_IRQ_CUSTOM))
free_irq(info->irq, idev);
uio_dev_del_attributes(idev);
device_destroy(&uio_class, MKDEV(uio_major, idev->minor));
kfree(idev);
return;
}
UIO FOPS
static int uio_open(struct inode *inode, struct file *filep)
{
struct uio_device *idev;
struct uio_listener *listener;
int ret = 0;
mutex_lock(&minor_lock);
根据Inode中的次设备号 在IDR中查找到文件对应的 uio_device
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;
}
listener = kmalloc(sizeof(*listener), GFP_KERNEL);
if (!listener) {
ret = -ENOMEM;
goto err_alloc_listener;
}
listener->dev = idev;
listener->event_count = atomic_read(&idev->event);
通过private_data指针实现私有数据隐藏
filep->private_data = listener;
设备注册时是否指定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;
}
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;
mi = uio_find_mem_index(vma);
if (mi < 0)
return -EINVAL;
requested_pages = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT;
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;
}
switch (idev->info->mem[mi].memtype) {
case UIO_MEM_PHYS:
mmap映射有两个方式:
直接映射所有页面
return uio_mmap_physical(vma);
case UIO_MEM_LOGICAL:
case UIO_MEM_VIRTUAL:
并不直接映射,而是设置vma中的 vm_ops接口, 在应用层访问内存 引发缺页异常是映射,缺页异常引发时 缺页异常处理程序调用vm_ops 中的fault函数指针指向的uio_vma_fault函数,该函数中查找确实的页并返回
return uio_mmap_logical(vma);
default:
return -EINVAL;
}
}