九鼎创展论坛

标题: x4412&ibox项目实战28-创建一个字符设备驱动 [打印本页]

作者: armeasy    时间: 2014-10-3 19:38
标题: x4412&ibox项目实战28-创建一个字符设备驱动
在前面的devfs文件系统的实验中,是使用register_chrdev函数注册的字符设备驱动。本章节通过cdev_init及cdev_add函数注册一个字符设备驱动,同时通过put_user和get_user函数实现内核和应用程序之间的简单数据交互。
在kernel/drivers/char/x4412目录下新建x4412-cdev.c文件,编辑内容如下:
  1. #include <linux/module.h>
  2. #include <linux/fs.h>
  3. #include <linux/errno.h>
  4. #include <linux/init.h>
  5. #include <linux/cdev.h>
  6. #include <asm/uaccess.h>
  7. #include <linux/device.h>
  8. static int x4412_cdev_major;
  9. static struct cdev x4412_cdev;
  10. static struct class *cdev_class;
  11. int x4412_cdev_open(struct inode *inode, struct file *filp)
  12. {
  13.          return 0;
  14. }
  15. int x4412_cdev_release(struct inode *inode, struct file *filp)
  16. {
  17.          return 0;
  18. }
  19. static long x4412_cdev_ioctl(struct file *file,unsigned int cmd, unsigned long arg)
  20. {
  21.          return 0;
  22. }
  23. static ssize_t x4412_cdev_read(struct file *filp, char __user *buf,size_t size,loff_t *ppos)
  24. {
  25.          int read_sizes = 0;
  26.          char *p;
  27.          char Message[]="This is x4412/ibox devboard.";

  28.          p = Message;
  29.          while(size && *p)
  30.          {
  31.                    if(put_user(*(p++),buf++))
  32.                             return -EINVAL;
  33.                    size--;
  34.                    read_sizes++;
  35.          }
  36.          return read_sizes;
  37. }
  38. static ssize_t x4412_cdev_write(struct file *filp, const char __user *buf,size_t size, loff_t *ppos)
  39. {
  40.          int i;
  41.          char str;
  42.          for(i=0;i<size;i++)
  43.          {
  44.                    if(!get_user(str,buf++))
  45.                             printk("%c",str);
  46.          }
  47.          printk("\r\n");
  48.          return size;
  49. }
  50. static const struct file_operations x4412_fops =
  51. {
  52.          .owner = THIS_MODULE,
  53.          .read = x4412_cdev_read,
  54.          .write = x4412_cdev_write,
  55.          .unlocked_ioctl = x4412_cdev_ioctl,
  56.          .open = x4412_cdev_open,
  57.          .release = x4412_cdev_release,
  58. };
  59. static void x4412_setup_cdev(void)
  60. {
  61.          int err, devno = MKDEV(x4412_cdev_major, 0);
  62.          cdev_init(&x4412_cdev, &x4412_fops);
  63.          err = cdev_add(&x4412_cdev, devno, 1);
  64.          if (err)
  65.                    printk(KERN_NOTICE "Error %d adding x4412_cdev", err);
  66. }

  67. static void x4412_clear_cdev(void)
  68. {
  69.          cdev_del(&x4412_cdev);
  70.          unregister_chrdev_region(MKDEV(x4412_cdev_major, 0), 1);
  71. }
  72. int x4412_cdev_init(void)
  73. {
  74.          int result;
  75.          dev_t devno;
  76.          result = alloc_chrdev_region(&devno, 0, 1, "x4412-cdev");
  77.          x4412_cdev_major = MAJOR(devno);
  78.          if(result < 0)
  79.          {
  80.                    printk("register x4412-cdev error!\r\n");
  81.                    return result;
  82.          }
  83.          printk("x4412-cdev main dev number:%d\r\n",x4412_cdev_major);
  84.          x4412_setup_cdev();
  85.          cdev_class = class_create(THIS_MODULE,"x4412-cdev");
  86.          if(IS_ERR(cdev_class))
  87.          {
  88.                    x4412_clear_cdev();
  89.                    return PTR_ERR(cdev_class);
  90.          }
  91.          device_create(cdev_class,NULL,MKDEV(x4412_cdev_major,0),NULL,"x4412-cdev");
  92.          return 0;
  93. }
  94. void x4412_cdev_exit(void)
  95. {
  96.          device_destroy(cdev_class,MKDEV(x4412_cdev_major,0));
  97.          class_destroy(cdev_class);
  98.          x4412_clear_cdev();
  99. }
  100. MODULE_AUTHOR("www.9tripod.com");
  101. MODULE_LICENSE("GPL");
  102. module_init(x4412_cdev_init);
  103. module_exit(x4412_cdev_exit);
复制代码
       在模块初始化函数x4412_cdev_init中,通过alloc_chrdev_region函数自动分配设备号,再通过MAJOR函数将主设备号保存到全局变量x4412_cdev_major中,供其他函数调用。紧接着在x4412_setup_cdev函数中,通过MKDEV函数获取设备号,cdev_initcdev_add函数实现file_operations相关函数的绑定和字符设备驱动的注册。class_createdevice_create函数用于创建设备节点。
       驱动的框架搭建完成后,开始给file_operations成员封装必要的函数。本实验需要通过get_userput_user函数实现内核和应用程序的数据交互,可以通过readwrite函数实现。read函数对应x4412_cdev_read,函数中定义了一个字符串数组Message,同时定义了一个指针指向该字符串,然后通过put_user函数将这个字符串传送给上层应用。write函数对应x4412_cdev_write,函数中定义了一个字符变量str,通过get_user函数从上层应用读取一串字符并打印出来。file_operations结构体的其他几个成员都直接返回0,不做任何操作。
       在模块卸载函数中,device_destroyclass_destroy函数用于卸载设备节点和/sysfs目录下生成的相关目录,cdev_del函数用于注销设备驱动,unregister_chrdev_region函数用于注销设备号。注意,这几个函数在卸载函数中的先后顺序千万不要颠倒,否则将会崩溃。
       kernel/drivers/char/x4412/Kconfig中添加如下语句:
  1. config X4412_CDEV_DRIVER
  2.          tristate "x4412 cdev driver"
  3.          default m
  4.          help
  5.          compile for x4412 cdev driver,y for kernel,m for module.
复制代码
       kernel/drivers/char/x4412/Makefile中添加如下语句:
  1. obj-$(CONFIG_X4412_CDEV_DRIVER) += x4412-cdev.o
复制代码
       编译内核,在kernel/drivers/char/x4412目录下将会生成驱动模块x4412-cdev.ko文件。
       ubuntu的用户目录或samba目录下新建应用程序x4412-cdev-app.c,编辑内容如下:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <fcntl.h>
  4. #define DEVICE_NAME  "/dev/x4412-cdev"

  5. int main(int argc,char **argv)
  6. {
  7.        int fd;
  8.             char buf[30];
  9.             char str[]="hello,x4412!";

  10.         fd = open(DEVICE_NAME,O_RDWR);
  11.         if(fd == -1)
  12.         {
  13.                   printf("open device %s error \n",DEVICE_NAME);
  14.         }
  15.         else
  16.         {
  17.                    read(fd,buf,30);
  18.                    printf("read from kernel: %s\r\n",buf);
  19.                    usleep(5000);
  20.                    printf("write value to kernel:\r\n");
  21.                    usleep(5000);
  22.                    write(fd,str,12);
  23.                    close(fd);
  24.         }
  25.         return 0;
  26. }
复制代码
       应用程序通过open函数打开驱动后,调用read函数从驱动中读取数据并打印出来,然后将字符串数组str的内容通过write函数写到驱动中,在驱动中将应用写入的字符串打印出来。
       执行如下指令编译应用程序:
  1. arm-none-linux-gnueabi-gcc x4412-cdev-app.c -o x4412-cdev-app
复制代码
       将映像文件x4412-cdev.kox4412-cdev-app拷贝到开发板,加载驱动测试:
  1. [root@x4412 mnt]# cd /sys/devices/virtual/
  2. [root@x4412 virtual]# ls
  3. android_usb/ input/       net/         sound/       vc/
  4. bdi/         mali/        ppp/         switch/      video4linux/
  5. block/       mem/         rc/          tty/         vtconsole/
  6. graphics/    misc/        regulator/   ump/
  7. [root@x4412 virtual]# insmod /mnt/x4412-cdev.ko
  8. [ 5602.304684] x4412-cdev main dev number:249
  9. [root@x4412 virtual]# ls
  10. android_usb/ input/       net/         sound/       vc/
  11. bdi/         mali/        ppp/         switch/      video4linux/
  12. block/       mem/         rc/          tty/         vtconsole/
  13. graphics/    misc/        regulator/   ump/         x4412-cdev/
  14. [root@x4412 virtual]#
复制代码
       可以看出,加载驱动后,在/sys/devices/virtual目录下生成了x4412-cdev目录。
  1. [root@x4412 x4412-cdev]# more /proc/devices |grep x4412
  2. 249 x4412-cdev
  3. [root@x4412 x4412-cdev]#
复制代码
       这里表明设备驱动的主设备号为249
  1. [root@x4412 x4412-cdev]# ls /dev/x4412-cdev -la
  2. crw-rw----    1 root     root      249,   0 Oct  3 09:47 /dev/x4412-cdev
  3. [root@x4412 x4412-cdev]#
复制代码
       查看/dev下的设备节点时,有可能并不存在,执行mdev –s指令将会触发节点的生成。前面表明设备驱动的主设备号为249,次设备号为0
       执行应用程序,观察现象是否和我们期望的一致:
  1. [root@x4412 mnt]# ./x4412-cdev-app
  2. read from kernel: This is x4412/ibox devboard.
  3. write value to kernel:
  4. [ 5858.796557] hello,x4412!
  5. [root@x4412 mnt]#
复制代码
       很明显,无论是应用程序还是驱动程序,都是按照我们的要求有条不紊的执行。再来分析cdev_initcdev_addregister_chrdev函数的区别。事实上,使用register_chrdev函数要居多,因为它用起来更加方便。事实上,register_chrdev函数的本质就是将cdev_alloccdev_add函数包装起来了,而cdev_alloc函数就相当于cdev_init函数,在kernel/fs/char_dev.c中可以看到register_chrdev的函数原型__register_chrdev()
  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.         
  12.          cdev = cdev_alloc();
  13.          if (!cdev)
  14.                    goto out2;

  15.          cdev->owner = fops->owner;
  16.          cdev->ops = fops;
  17.          kobject_set_name(&cdev->kobj, "%s", name);
  18.                   
  19.          err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
  20.          if (err)
  21.                    goto out;

  22.          cd->cdev = cdev;

  23.          return major ? 0 : cd->major;
  24. out:
  25.          kobject_put(&cdev->kobj);
  26. out2:
  27.          kfree(__unregister_chrdev_region(cd->major, baseminor, count));
  28.          return err;
  29. }
复制代码

作者: caoyimeng    时间: 2014-11-28 18:30
很好,学习了




欢迎光临 九鼎创展论坛 (http://bbs.9tripod.com/) Powered by Discuz! X3.2