• “Linux 驱动 = 软件框架 + 硬件操作”
  • LED驱动框架
    • image.png
  • 驱动程序依赖于 Linux 内核,你为开发板 A 开发驱动,那就先在 Ubuntu 中得到、配置、编译开发板 A所使用的 Linux 内核
  • APP 打开文件时,可以得到一个整数,这个整数被称为文件句柄。对于 APP 的每一个文件句柄,在内核里面都有一个“struct file”与之对应。 — 文件句柄就是第几个struct file结构体
    • image.png
    • 使用 open 打开文件时,传入的 flags、mode 等参数会被记录在内核中对应的 struc file 结构体里(f_flags、f_mode)
  • 打开字符设备节点时,内核中也有对应的 struct file
    • struct file结构体中的结构体struct file_operations *f_op,这是由驱动程序提供的
    • image.png
    • struct file_operations *f_op结构体中定义了open、read、write等函数
    • image.png
  • 怎么编写驱动程序?

① 确定主设备号,也可以让内核分配
② 定义自己的 file_operations 结构体
③ 实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体
④ 把 file_operations 结构体告诉内核:register_chrdev
⑤ 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
⑥ 有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用 unregister_chrdev
⑦ 其他完善:提供设备信息,自动创建设备节点:class_create, device_create

  • 写驱动程序,建议先下载、解压linux内核源码,再SI中建立工程,最后再把要编写的驱动程序加到工程中编辑;因为驱动程序是给内核使用的,会用到内核提供的很多函数,再内核的工程中编写代码会比较方便

  • 参考内核已有的驱动程序写hello驱动 ```c / 1. 确定主设备号 / static int major = 0; static char kernel_buf[1024]; / 在内核中的缓冲区buffer / static struct class *hello_class;

define MIN(a, b) (a < b ? a : b)

/ 3. 实现对应的open/read/write等函数,填入file_operations结构体 / /* file : 文件句柄

  • __user是一个空的宏,表示用户空间,不可以直接访问,需要通过copy_to_user/copy_from_usr这些函数来访问
  • size, offset / static ssize_t hello_drv_read (struct file file, char user buf, size_t size, loff_t offset) { int err; printk(“%s %s line %d\n”, FILE, FUNCTION, LINE__); / 宏:当前文件、当前函数、当前行 / err = copy_to_user(buf, kernel_buf, MIN(1024, size)); / 从内核空间拷贝到用户空间 / return MIN(1024, size); }

static ssizet hellodrvwrite (struct file file, const char __user buf, sizet size, lofft *offset) { int err; printk(“%s %s line %d\n”, _FILE, __FUNCTION, __LINE); err = copy_from_user(kernel_buf, buf, MIN(1024, size)); return MIN(1024, size); }

static int hellodrvopen (struct inode node, struct file file) { printk(“%s %s line %d\n”, FILE, FUNCTION, __LINE); return 0; }

static int hellodrvclose (struct inode node, struct file file) { printk(“%s %s line %d\n”, FILE, FUNCTION, __LINE); return 0; }

/ 2. 定义自己的file_operations结构体 / static struct file_operations hello_drv = { .owner = THIS_MODULE, / 表示属于该模块 / .open = hello_drv_open, .read = hello_drv_read, .write = hello_drv_write, .release = hello_drv_close, };

/ 4. 把file_operations结构体告诉内核:注册驱动程序 / / 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 / static int __init hello_init(void) { int err;

  1. printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  2. /* 注册驱动程序,告诉内核 */
  3. major = register_chrdev(0, "hello", &hello_drv); /* 应用访问/dev/hello */
  4. /* 应用程序想访问该驱动程序时,需要通过设备节点/dev/hello来进行;我们需要提供某些信息来创建该设备节点 */
  5. /* 先创建一个类hello_class */
  6. hello_class = class_create(THIS_MODULE, "hello_class");
  7. err = PTR_ERR(hello_class);
  8. if (IS_ERR(hello_class)) {
  9. printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
  10. unregister_chrdev(major, "hello");
  11. return -1;
  12. }
  13. /* 在hello_class类下创建设备节点,名称为"hello" */
  14. device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); /* /dev/hello */
  15. return 0;

}

/ 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数 / static void exit hello_exit(void) { printk(“%s %s line %d\n”, FILE, FUNCTION, LINE__); device_destroy(hello_class, MKDEV(major, 0)); class_destroy(hello_class); / 注销驱动程序 / unregister_chrdev(major, “hello”); }

/ 7. 其他完善:提供设备信息,自动创建设备节点 /

module_init(hello_init); / 用宏修饰hello_init函数为入口函数 / module_exit(hello_exit); / 用宏修饰hello_exit函数为出口函数 /

MODULE_LICENSE(“GPL”); / 遵守GPL协议声明,只有这样才能使用内核的相关函数/代码 / ```

  • 驱动程序是给内核用的,它严重依赖于内核的源码,需要借助内核源码来编译

    cat /proc/device 可查看设备驱动 lsmod 查看内核中已加载的模块

  • 内核中使用printk来打印

  • 可以使用dmesg命令来查看打印信息
  • 有些内核启动时,修改某些配置文件,禁止了内核把打印信息从串口打印出来

补充:
image.png