当前位置:   article > 正文

Linux——驱动:字符设备驱动框架

字符设备驱动框架

1.字符设备理论源码分析

字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、 IIC、 SPI,LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。在用户空间控制一个字符设备的流程如图所示。

 例如在用户空间中调用open,打开一个字符设备,执行流程如下:最终会执行chrdev中的ops对应的open函数。

 1.1 字符设备的注册

字符设备相关的结构体:

  1. static struct char_device_struct {
  2. struct char_device_struct *next;
  3. unsigned int major;
  4. unsigned int baseminor;
  5. int minorct;
  6. char name[64];
  7. struct cdev *cdev; /* will die */
  8. } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
  9. #define CHRDEV_MAJOR_HASH_SIZE 255
  10. struct cdev {
  11. struct kobject kobj;
  12. struct module *owner;
  13. const struct file_operations *ops;
  14. struct list_head list;
  15. dev_t dev;
  16. unsigned int count;
  17. };

其中chrdevs为一个全局数组,且:

#define CHRDEV_MAJOR_HASH_SIZE	255

1.1.1旧版本:申请设备号

旧版本的注册函数为:

  1. static inline int register_chrdev(unsigned int major, const char *name,
  2. const struct file_operations *fops)
  3. static inline int register_chrdev(unsigned int major, const char *name,
  4. const struct file_operations *fops)
  5. {
  6. return __register_chrdev(major, 0, 256, name, fops);
  7. }

通过调用__register_chrdev(major, 0, 256, name, fops)来进行注册:

  1. int __register_chrdev(unsigned int major, unsigned int baseminor,
  2. unsigned int count, const char *name,
  3. const struct file_operations *fops)
  4. {
  5. struct char_device_struct *cd;
  6. struct cdev *cdev;
  7. int err = -ENOMEM;
  8. cd = __register_chrdev_region(major, baseminor, count, name);
  9. if (IS_ERR(cd))
  10. return PTR_ERR(cd);
  11. cdev = cdev_alloc();
  12. if (!cdev)
  13. goto out2;
  14. cdev->owner = fops->owner;
  15. cdev->ops = fops;
  16. kobject_set_name(&cdev->kobj, "%s", name);
  17. err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
  18. if (err)
  19. goto out;
  20. cd->cdev = cdev;
  21. return major ? 0 : cd->major;
  22. out:
  23. kobject_put(&cdev->kobj);
  24. out2:
  25. kfree(__unregister_chrdev_region(cd->major, baseminor, count));
  26. return err;
  27. }
  1. static struct char_device_struct *
  2. __register_chrdev_region(unsigned int major, unsigned int baseminor,
  3. int minorct, const char *name)
  4. {
  5. struct char_device_struct *cd, **cp;
  6. int ret = 0;
  7. int i;
  8. cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
  9. if (cd == NULL)
  10. return ERR_PTR(-ENOMEM);
  11. mutex_lock(&chrdevs_lock);
  12. /* temporary */
  13. if (major == 0) { //如果没有指定主设备号
  14. for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
  15. if (chrdevs[i] == NULL)
  16. break;
  17. }
  18. if (i == 0) {
  19. ret = -EBUSY;
  20. goto out;
  21. }
  22. major = i;
  23. }
  24. cd->major = major;
  25. cd->baseminor = baseminor;
  26. cd->minorct = minorct;
  27. strlcpy(cd->name, name, sizeof(cd->name));
  28. i = major_to_index(major);
  29. for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) //寻找空的子设备位置
  30. if ((*cp)->major > major ||
  31. ((*cp)->major == major &&
  32. (((*cp)->baseminor >= baseminor) ||
  33. ((*cp)->baseminor + (*cp)->minorct > baseminor))))
  34. break;
  35. /* Check for overlapping minor ranges. */
  36. if (*cp && (*cp)->major == major) {
  37. int old_min = (*cp)->baseminor;
  38. int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
  39. int new_min = baseminor;
  40. int new_max = baseminor + minorct - 1;
  41. /* New driver overlaps from the left. */
  42. if (new_max >= old_min && new_max <= old_max) {
  43. ret = -EBUSY;
  44. goto out;
  45. }
  46. /* New driver overlaps from the right. */
  47. if (new_min <= old_max && new_min >= old_min) {
  48. ret = -EBUSY;
  49. goto out;
  50. }
  51. }
  52. cd->next = *cp;
  53. *cp = cd;
  54. mutex_unlock(&chrdevs_lock);
  55. return cd;
  56. out:
  57. mutex_unlock(&chrdevs_lock);
  58. kfree(cd);
  59. return ERR_PTR(ret);
  60. }

1.1.2新版本:申请设备号

(1)提前指定了设备号:可以通过MKDEV(major)获取dev_t。

  1. int register_chrdev_region(dev_t from, unsigned count, const char *name)
  2. {
  3. struct char_device_struct *cd;
  4. dev_t to = from + count;
  5. dev_t n, next;
  6. for (n = from; n < to; n = next) {
  7. next = MKDEV(MAJOR(n)+1, 0);
  8. if (next > to)
  9. next = to;
  10. cd = __register_chrdev_region(MAJOR(n), MINOR(n),
  11. next - n, name);
  12. if (IS_ERR(cd))
  13. goto fail;
  14. }
  15. return 0;
  16. fail:
  17. to = n;
  18. for (n = from; n < to; n = next) {
  19. next = MKDEV(MAJOR(n)+1, 0);
  20. kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
  21. }
  22. return PTR_ERR(cd);
  23. }

(2)没有指定主设备号,由系统动态分配:

  1. int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
  2. const char *name)
  3. {
  4. struct char_device_struct *cd;
  5. cd = __register_chrdev_region(0, baseminor, count, name);
  6. if (IS_ERR(cd))
  7. return PTR_ERR(cd);
  8. *dev = MKDEV(cd->major, cd->baseminor);
  9. return 0;
  10. }

(3)注销字符设备

  1. void unregister_chrdev_region(dev_t from, unsigned count)
  2. {
  3. dev_t to = from + count;
  4. dev_t n, next;
  5. for (n = from; n < to; n = next) {
  6. next = MKDEV(MAJOR(n)+1, 0);
  7. if (next > to)
  8. next = to;
  9. kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
  10. }
  11. }

区别:

1.1.3 字符设备初始化

调用cdev_init()函数来把cdev与ops进行绑定:

  1. void cdev_init(struct cdev *cdev, const struct file_operations *fops)
  2. {
  3. memset(cdev, 0, sizeof *cdev);
  4. INIT_LIST_HEAD(&cdev->list);
  5. kobject_init(&cdev->kobj, &ktype_cdev_default);
  6. cdev->ops = fops;
  7. }

1.1.4 添加字符设备

通过调用cdev_add将申请到的cdev添加到与之对应的设备号下:

  1. int cdev_add(struct cdev *p, dev_t dev, unsigned count)
  2. {
  3. int error;
  4. p->dev = dev;
  5. p->count = count;
  6. error = kobj_map(cdev_map, dev, count, NULL,
  7. exact_match, exact_lock, p);
  8. if (error)
  9. return error;
  10. kobject_get(p->kobj.parent);
  11. return 0;
  12. }

注销该设备的适合,需要调用cdev_del将其删掉:

  1. void cdev_del(struct cdev *p)
  2. {
  3. cdev_unmap(p->dev, p->count);
  4. kobject_put(&p->kobj);
  5. }

1.2 调用modprobe指令后自动创建设备

1.2.1 创建类class

通过调用class_create()函数进行创建类,在sys/class/下创建一个文件,并且通过udev的uevent机制通知有设备添加进来。

  1. #define class_create(owner, name) \
  2. ({ \
  3. static struct lock_class_key __key; \
  4. __class_create(owner, name, &__key); \
  5. })
  1. struct class *__class_create(struct module *owner, const char *name,
  2. struct lock_class_key *key)
  3. {
  4. struct class *cls;
  5. int retval;
  6. cls = kzalloc(sizeof(*cls), GFP_KERNEL);
  7. if (!cls) {
  8. retval = -ENOMEM;
  9. goto error;
  10. }
  11. cls->name = name;
  12. cls->owner = owner;
  13. cls->class_release = class_create_release;
  14. retval = __class_register(cls, key);
  15. if (retval)
  16. goto error;
  17. return cls;
  18. error:
  19. kfree(cls);
  20. return ERR_PTR(retval);
  21. }
  1. int __class_register(struct class *cls, struct lock_class_key *key)
  2. {
  3. struct subsys_private *cp;
  4. int error;
  5. pr_debug("device class '%s': registering\n", cls->name);
  6. cp = kzalloc(sizeof(*cp), GFP_KERNEL);//类同bus,分配一个私有数据
  7. if (!cp)
  8. return -ENOMEM;
  9. klist_init(&cp->klist_devices, klist_class_dev_get, klist_class_dev_put);//初始化class 链表
  10. INIT_LIST_HEAD(&cp->interfaces);
  11. kset_init(&cp->glue_dirs);
  12. __mutex_init(&cp->mutex, "subsys mutex", key);
  13. error = kobject_set_name(&cp->subsys.kobj, "%s", cls->name);
  14. if (error) {
  15. kfree(cp);
  16. return error;
  17. }
  18. /* set the default /sys/dev directory for devices of this class */
  19. if (!cls->dev_kobj)
  20. cls->dev_kobj = sysfs_dev_char_kobj;
  21. #if defined(CONFIG_BLOCK)
  22. /* let the block class directory show up in the root of sysfs */
  23. if (!sysfs_deprecated || cls != &block_class)
  24. cp->subsys.kobj.kset = class_kset;
  25. #else
  26. cp->subsys.kobj.kset = class_kset;
  27. #endif
  28. cp->subsys.kobj.ktype = &class_ktype;
  29. cp->class = cls;
  30. cls->p = cp;
  31. //在/sys/class/下面创建一个目录,同时发送一个uenent时间给上层
  32. error = kset_register(&cp->subsys);
  33. if (error) {
  34. kfree(cp);
  35. return error;
  36. }
  37. error = add_class_attrs(class_get(cls));
  38. class_put(cls);
  39. return error;
  40. }
  1. int kset_register(struct kset *k)
  2. {
  3. int err;
  4. if (!k)
  5. return -EINVAL;
  6. //初始化kset
  7. kset_init(k);
  8. //在sysfs中建立目录
  9. err = kobject_add_internal(&k->kobj);
  10. if (err)
  11. return err;
  12. // 向用户空间发送 KOBJ_ADD事件。
  13. kobject_uevent(&k->kobj, KOBJ_ADD);
  14. return 0;
  15. }

当注销设备时,需要调用class_destory()函数对class进行摧毁:

  1. int kset_register(struct kset *k)
  2. {
  3. int err;
  4. if (!k)
  5. return -EINVAL;
  6. //初始化kset
  7. kset_init(k);
  8. //在sysfs中建立目录
  9. err = kobject_add_internal(&k->kobj);
  10. if (err)
  11. return err;
  12. // 向用户空间发送 KOBJ_ADD事件。
  13. kobject_uevent(&k->kobj, KOBJ_ADD);
  14. return 0;
  15. }

1.2.2 创建设备

一旦创建好了这个class,再调用device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点

  1. /* 5、创建设备 */
  2. newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
  3. struct device *device_create(struct class *class, struct device *parent,
  4. dev_t devt, void *drvdata, const char *fmt, ...)
  5. {
  6. va_list vargs;
  7. struct device *dev;
  8. va_start(vargs, fmt);
  9. dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
  10. va_end(vargs);
  11. return dev;
  12. }
  13. struct device *device_create_vargs(struct class *class, struct device *parent,
  14. dev_t devt, void *drvdata, const char *fmt,
  15. va_list args)
  16. {
  17. return device_create_groups_vargs(class, parent, devt, drvdata, NULL,
  18. fmt, args);
  19. }
  1. static struct device *
  2. device_create_groups_vargs(struct class *class, struct device *parent,
  3. dev_t devt, void *drvdata,
  4. const struct attribute_group **groups,
  5. const char *fmt, va_list args)
  6. {
  7. struct device *dev = NULL;
  8. int retval = -ENODEV;
  9. if (class == NULL || IS_ERR(class))
  10. goto error;
  11. dev = kzalloc(sizeof(*dev), GFP_KERNEL);
  12. if (!dev) {
  13. retval = -ENOMEM;
  14. goto error;
  15. }
  16. //初始化dev
  17. device_initialize(dev);
  18. dev->devt = devt;
  19. dev->class = class;
  20. dev->parent = parent;
  21. dev->groups = groups;
  22. dev->release = device_create_release;
  23. dev_set_drvdata(dev, drvdata);
  24. //设置kobject的名字
  25. retval = kobject_set_name_vargs(&dev->kobj, fmt, args);
  26. if (retval)
  27. goto error;
  28. retval = device_add(dev); //添加设备
  29. if (retval)
  30. goto error;
  31. return dev;
  32. error:
  33. put_device(dev);
  34. return ERR_PTR(retval);
  35. }
  1. int device_add(struct device *dev)
  2. {
  3. struct device *parent = NULL;
  4. struct kobject *kobj;
  5. struct class_interface *class_intf;
  6. int error = -EINVAL;
  7. dev = get_device(dev); //增加设备的引用计数dev->kobj->kref
  8. if (!dev)
  9. goto done;
  10. if (!dev->p) {
  11. //私有数据没有的话,申请并初始化:是连接bus,parent,对应驱动等重要连接点
  12. error = device_private_init(dev);
  13. if (error)
  14. goto done;
  15. }
  16. /*
  17. * for statically allocated devices, which should all be converted
  18. * some day, we need to initialize the name. We prevent reading back
  19. * the name, and force the use of dev_name()
  20. */
  21. if (dev->init_name) {
  22. //用dev的init_name初始化dev->kobject->name,实际是目录名
  23. dev_set_name(dev, "%s", dev->init_name);
  24. dev->init_name = NULL;
  25. }
  26. /* subsystems can specify simple device enumeration */
  27. //dev的init_name不存在且dev->kobject->name也不存在,则用bus的dev_name和dev_id来设置目录名
  28. if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
  29. dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);
  30. if (!dev_name(dev)) { //如果上面几个步骤都还没找到可设的目录名,则失败返回,设备必须要放在某个目录下
  31. error = -EINVAL;
  32. goto name_error;
  33. }
  34. pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
  35. parent = get_device(dev->parent); //父节点引用计数加1
  36. kobj = get_device_parent(dev, parent); //拿到父节点
  37. //拿到父节点赋值给本dev->kobj.parent,确定设备父子关系,也确定了sysfs中的目录关系
  38. if (kobj)
  39. dev->kobj.parent = kobj;
  40. /* use parent numa_node */
  41. //设置该设备节点为-1
  42. if (parent)
  43. set_dev_node(dev, dev_to_node(parent));
  44. /* first, register with generic layer. */
  45. /* we require the name to be set before, and pass NULL */
  46. //把内嵌的kobject注册到设备模型中,将设备加入到kobject模型中,创建sys项目目录,目录名字为kobj->name
  47. error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
  48. if (error)
  49. goto Error;
  50. /* notify platform of device entry */
  51. if (platform_notify)
  52. platform_notify(dev);
  53. /*创建sys目录下设备的uevent属性文件,通过它可以查看设备的uevent事件,主要是在/sys/devices/.../中添加dev的uevent属性文件*/
  54. error = device_create_file(dev, &dev_attr_uevent);
  55. if (error)
  56. goto attrError;
  57. /*实际创建的kobject都是在device下面,其他class,bus之类的里面的具体设备都是device目录下设备的符号链接,这里是在class下创建符号链接 */
  58. error = device_add_class_symlinks(dev);
  59. if (error)
  60. goto SymlinkError;
  61. error = device_add_attrs(dev);//创建sys目录下设备其他属性文件(添加设备属性文件)
  62. if (error)
  63. goto AttrsError;
  64. error = bus_add_device(dev);//添加设备的总线属性,将设备加入到管理它的bus总线的设备连表上,创建subsystem链接文件,链接class下的具体的子系统文件夹 将设备添加到其总线的设备列表中。
  65. if (error)
  66. goto BusError;
  67. error = dpm_sysfs_add(dev);//把设备增加到sysfs电源管理power目录(组)下,如果该设备设置电源管理相关的内容
  68. if (error)
  69. goto DPMError;
  70. device_pm_add(dev);//设备添加到电源管理相关的设备列表中
  71. //主设备号存在,则产生dev属性,在/dev目录下产生设备节点文件
  72. if (MAJOR(dev->devt)) {
  73. /*创建sys目录下设备的设备号属性,即major和minor /主要是在sys/devices/...中添加dev属性文件*/
  74. error = device_create_file(dev, &dev_attr_dev);
  75. if (error)
  76. goto DevAttrError;
  77. /*在/sys/dev/char/或者/sys/dev/block/创建devt的属性的连接文件,形如10:45,由主设备号和次设备号构成,指向/sys/devices/.../的具体设备目录*/
  78. error = device_create_sys_dev_entry(dev);//该链接文件只具备读属性,显示主设备号:次设备号,如10:45,用户空间udev响应uevent事件时,将根据设备号在/dev下创建节点文件
  79. if (error)
  80. goto SysEntryError;
  81. devtmpfs_create_node(dev);
  82. }
  83. /* Notify clients of device addition. This call must come
  84. * after dpm_sysfs_add() and before kobject_uevent().
  85. */
  86. if (dev->bus)//通知客户端,有新设备加入
  87. blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
  88. BUS_NOTIFY_ADD_DEVICE, dev);
  89. /*产生一个内核uevent事件(这里是有设备加入),可以是helper,也可是通过netlink机制和用户空间通信该事件可以被内核以及应用层捕获,属于linux设备模型中热插拔机制*/
  90. kobject_uevent(&dev->kobj, KOBJ_ADD);
  91. //给设备探测寻找相对应的驱动,在bus上找dev对应的drv,主要执行__device_attach,主要进行match,sys_add,执行probe函数和绑定等操作
  92. bus_probe_device(dev);
  93. if (parent)
  94. klist_add_tail(&dev->p->knode_parent,//添加新设备到父设备的子列表中
  95. &parent->p->klist_children);
  96. if (dev->class) {//如果改dev有所属类,则将dev的添加到类的设备列表里面
  97. mutex_lock(&dev->class->p->mutex);//要使用class的互斥锁
  98. /* tie the class to the device */
  99. klist_add_tail(&dev->knode_class,//dev添加到class的klist_device链表(对driver也有klist_driver链表)
  100. &dev->class->p->klist_devices);
  101. /* notify any interfaces that the device is here */
  102. /*通知有新设备加入,执行该dev的class_intf->add_dev(),好处是只有设备匹配注册成功了,才进行其它的注册工作(如字符设备的注册,生成/dev/***节点文件)以及部分初始化工作。*/
  103. list_for_each_entry(class_intf,
  104. &dev->class->p->interfaces, node)
  105. if (class_intf->add_dev)
  106. class_intf->add_dev(dev, class_intf);
  107. mutex_unlock(&dev->class->p->mutex);
  108. }
  109. done:
  110. put_device(dev);
  111. return error;
  112. SysEntryError:
  113. if (MAJOR(dev->devt))
  114. device_remove_file(dev, &dev_attr_dev);
  115. DevAttrError:
  116. device_pm_remove(dev);
  117. dpm_sysfs_remove(dev);
  118. DPMError:
  119. bus_remove_device(dev);
  120. BusError:
  121. device_remove_attrs(dev);
  122. AttrsError:
  123. device_remove_class_symlinks(dev);
  124. SymlinkError:
  125. device_remove_file(dev, &dev_attr_uevent);
  126. attrError:
  127. kobject_uevent(&dev->kobj, KOBJ_REMOVE);
  128. kobject_del(&dev->kobj);
  129. Error:
  130. cleanup_device_parent(dev);
  131. put_device(parent);
  132. name_error:
  133. kfree(dev->p);
  134. dev->p = NULL;
  135. goto done;
  136. }

同样地,在注销字符设备的适合,需要调用device_destroy()函数进行设备注销:

  1. void device_destroy(struct class *class, dev_t devt)
  2. {
  3. struct device *dev;
  4. dev = class_find_device(class, NULL, &devt, __match_devt);
  5. if (dev) {
  6. put_device(dev);
  7. device_unregister(dev);
  8. }
  9. }

3.字符设备实践

  1. #include <linux/types.h>
  2. #include <linux/kernel.h>
  3. #include <linux/delay.h>
  4. #include <linux/ide.h>
  5. #include <linux/init.h>
  6. #include <linux/module.h>
  7. #include <linux/errno.h>
  8. #include <linux/gpio.h>
  9. #include <linux/cdev.h>
  10. #include <linux/device.h>
  11. #include <asm/mach/map.h>
  12. #include <asm/uaccess.h>
  13. #include <asm/io.h>
  14. #define NEWCHRLED_CNT 1 /* 设备号个数 */
  15. #define NEWCHRLED_NAME "newchrled" /* 名字 */
  16. #define LEDOFF 0 /* 关灯 */
  17. #define LEDON 1 /* 开灯 */
  18. /* 寄存器物理地址 */
  19. #define CCM_CCGR1_BASE (0X020C406C)
  20. #define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
  21. #define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
  22. #define GPIO1_DR_BASE (0X0209C000)
  23. #define GPIO1_GDIR_BASE (0X0209C004)
  24. /* 映射后的寄存器虚拟地址指针 */
  25. static void __iomem *IMX6U_CCM_CCGR1;
  26. static void __iomem *SW_MUX_GPIO1_IO03;
  27. static void __iomem *SW_PAD_GPIO1_IO03;
  28. static void __iomem *GPIO1_DR;
  29. static void __iomem *GPIO1_GDIR;
  30. /* newchrled设备结构体 */
  31. struct newchrled_dev{
  32. dev_t devid; /* 设备号 */
  33. struct cdev cdev; /* cdev */
  34. struct class *class; /* 类 */
  35. struct device *device; /* 设备 */
  36. int major; /* 主设备号 */
  37. int minor; /* 次设备号 */
  38. };
  39. struct newchrled_dev newchrled; /* led设备 */
  40. /*
  41. * @description : LED打开/关闭
  42. * @param - sta : LEDON(0) 打开LED,LEDOFF(1) 关闭LED
  43. * @return : 无
  44. */
  45. void led_switch(u8 sta)
  46. {
  47. u32 val = 0;
  48. if(sta == LEDON) {
  49. val = readl(GPIO1_DR);
  50. val &= ~(1 << 3);
  51. writel(val, GPIO1_DR);
  52. }else if(sta == LEDOFF) {
  53. val = readl(GPIO1_DR);
  54. val|= (1 << 3);
  55. writel(val, GPIO1_DR);
  56. }
  57. }
  58. /*
  59. * @description : 打开设备
  60. * @param - inode : 传递给驱动的inode
  61. * @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
  62. * 一般在open的时候将private_data指向设备结构体。
  63. * @return : 0 成功;其他 失败
  64. */
  65. static int led_open(struct inode *inode, struct file *filp)
  66. {
  67. filp->private_data = &newchrled; /* 设置私有数据 */
  68. return 0;
  69. }
  70. /*
  71. * @description : 从设备读取数据
  72. * @param - filp : 要打开的设备文件(文件描述符)
  73. * @param - buf : 返回给用户空间的数据缓冲区
  74. * @param - cnt : 要读取的数据长度
  75. * @param - offt : 相对于文件首地址的偏移
  76. * @return : 读取的字节数,如果为负值,表示读取失败
  77. */
  78. static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
  79. {
  80. return 0;
  81. }
  82. /*
  83. * @description : 向设备写数据
  84. * @param - filp : 设备文件,表示打开的文件描述符
  85. * @param - buf : 要写给设备写入的数据
  86. * @param - cnt : 要写入的数据长度
  87. * @param - offt : 相对于文件首地址的偏移
  88. * @return : 写入的字节数,如果为负值,表示写入失败
  89. */
  90. static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
  91. {
  92. int retvalue;
  93. unsigned char databuf[1];
  94. unsigned char ledstat;
  95. retvalue = copy_from_user(databuf, buf, cnt);
  96. if(retvalue < 0) {
  97. printk("kernel write failed!\r\n");
  98. return -EFAULT;
  99. }
  100. ledstat = databuf[0]; /* 获取状态值 */
  101. if(ledstat == LEDON) {
  102. led_switch(LEDON); /* 打开LED灯 */
  103. } else if(ledstat == LEDOFF) {
  104. led_switch(LEDOFF); /* 关闭LED灯 */
  105. }
  106. return 0;
  107. }
  108. /*
  109. * @description : 关闭/释放设备
  110. * @param - filp : 要关闭的设备文件(文件描述符)
  111. * @return : 0 成功;其他 失败
  112. */
  113. static int led_release(struct inode *inode, struct file *filp)
  114. {
  115. return 0;
  116. }
  117. /* 设备操作函数 */
  118. static struct file_operations newchrled_fops = {
  119. .owner = THIS_MODULE,
  120. .open = led_open,
  121. .read = led_read,
  122. .write = led_write,
  123. .release = led_release,
  124. };
  125. /*
  126. * @description : 驱动出口函数
  127. * @param : 无
  128. * @return : 无
  129. */
  130. static int __init led_init(void)
  131. {
  132. u32 val = 0;
  133. /* 初始化LED */
  134. /* 1、寄存器地址映射 */
  135. IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
  136. SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
  137. SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
  138. GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
  139. GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);
  140. /* 2、使能GPIO1时钟 */
  141. val = readl(IMX6U_CCM_CCGR1);
  142. val &= ~(3 << 26); /* 清除以前的设置 */
  143. val |= (3 << 26); /* 设置新值 */
  144. writel(val, IMX6U_CCM_CCGR1);
  145. /* 3、设置GPIO1_IO03的复用功能,将其复用为
  146. * GPIO1_IO03,最后设置IO属性。
  147. */
  148. writel(5, SW_MUX_GPIO1_IO03);
  149. /*寄存器SW_PAD_GPIO1_IO03设置IO属性
  150. *bit 16:0 HYS关闭
  151. *bit [15:14]: 00 默认下拉
  152. *bit [13]: 0 kepper功能
  153. *bit [12]: 1 pull/keeper使能
  154. *bit [11]: 0 关闭开路输出
  155. *bit [7:6]: 10 速度100Mhz
  156. *bit [5:3]: 110 R0/6驱动能力
  157. *bit [0]: 0 低转换率
  158. */
  159. writel(0x10B0, SW_PAD_GPIO1_IO03);
  160. /* 4、设置GPIO1_IO03为输出功能 */
  161. val = readl(GPIO1_GDIR);
  162. val &= ~(1 << 3); /* 清除以前的设置 */
  163. val |= (1 << 3); /* 设置为输出 */
  164. writel(val, GPIO1_GDIR);
  165. /* 5、默认关闭LED */
  166. val = readl(GPIO1_DR);
  167. val |= (1 << 3);
  168. writel(val, GPIO1_DR);
  169. /* 注册字符设备驱动 */
  170. /* 1、创建设备号 */
  171. if (newchrled.major) { /* 定义了设备号 */
  172. newchrled.devid = MKDEV(newchrled.major, 0);
  173. register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
  174. } else { /* 没有定义设备号 */
  175. alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME); /* 申请设备号 */
  176. newchrled.major = MAJOR(newchrled.devid); /* 获取分配号的主设备号 */
  177. newchrled.minor = MINOR(newchrled.devid); /* 获取分配号的次设备号 */
  178. }
  179. printk("newcheled major=%d,minor=%d\r\n",newchrled.major, newchrled.minor);
  180. /* 2、初始化cdev */
  181. newchrled.cdev.owner = THIS_MODULE;
  182. cdev_init(&newchrled.cdev, &newchrled_fops);
  183. /* 3、添加一个cdev */
  184. cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);
  185. /* 4、创建类 */
  186. newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
  187. if (IS_ERR(newchrled.class)) {
  188. return PTR_ERR(newchrled.class);
  189. }
  190. /* 5、创建设备 */
  191. newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
  192. if (IS_ERR(newchrled.device)) {
  193. return PTR_ERR(newchrled.device);
  194. }
  195. return 0;
  196. }
  197. /*
  198. * @description : 驱动出口函数
  199. * @param : 无
  200. * @return : 无
  201. */
  202. static void __exit led_exit(void)
  203. {
  204. /* 取消映射 */
  205. iounmap(IMX6U_CCM_CCGR1);
  206. iounmap(SW_MUX_GPIO1_IO03);
  207. iounmap(SW_PAD_GPIO1_IO03);
  208. iounmap(GPIO1_DR);
  209. iounmap(GPIO1_GDIR);
  210. /* 注销字符设备驱动 */
  211. cdev_del(&newchrled.cdev);/* 删除cdev */
  212. unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); /* 注销设备号 */
  213. device_destroy(newchrled.class, newchrled.devid);
  214. class_destroy(newchrled.class);
  215. }
  216. module_init(led_init);
  217. module_exit(led_exit);
  218. MODULE_LICENSE("GPL");

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/180463?site
推荐阅读
相关标签
  

闽ICP备14008679号