本帖最后由 andeyqi 于 2023-11-23 22:29 编辑  
 
简介: 
linux 的驱动程序一般是在内核态,用户在用户态通过虚拟文件系统VFS创建的文件节点的,open/read/write 等方法访问驱动程序,字符设备驱动程序是最基本的驱动模型,用户态通过系统调用访问内核态的驱动程序,那开始我们的主题字符驱动程序的学习。 
 
1.内核模块代码编写 
 
用户态调用内核态的驱动程序流程如下: 
 
我们编写内核态测试代码通过模块加载的方式进行加载,open 函数只是打印log 用于确认代码已经被调用到,代码如下:  
- #include <linux/module.h>
 
 - #include <linux/fs.h>
 
 - #include <linux/device.h>
 
 - #include <linux/cdev.h>
 
 - #include <linux/types.h>
 
 - #include <linux/kdev_t.h>
 
 - #include <linux/uaccess.h>
 
  
- #define DEV_MEM_SIZE 1024
 
 - char device_buffer[DEV_MEM_SIZE];
 
 - static int pseudo_chr_dev_open(struct inode *inode, struct file *filp)
 
 - {
 
 -     pr_info("open was successful\n");
 
 -     return 0;
 
 - }
 
  复制代码 
 
wirte 函数将用户态态的数据copy 至缓冲区域device_buffer保存  
- static ssize_t pseudo_chr_dev_write(struct file *filp, const char __user *buff, size_t count, loff_t *f_pos)
 
 - {
 
 -     pr_info("write requested for %zu bytes\n", count);
 
  
-     if((*f_pos + count) > DEV_MEM_SIZE)
 
 -     {
 
 -         count = DEV_MEM_SIZE - *f_pos;
 
 -     }
 
  
-     if(!count)
 
 -     {
 
 -         return -ENOMEM;
 
 -     }
 
  
-     if(copy_from_user(&device_buffer[*f_pos], buff, count))
 
 -     {
 
 -         return -EFAULT;
 
 -     }
 
  
-     pr_info("Number of bytes successfully written = %zu\n", count);
 
 -     return count;
 
 - }
 
  复制代码 
 
read 函数将缓冲区device_buffer的数据返回至用户空间  
- static ssize_t pseudo_chr_dev_read(struct file *filp, char __user *buff, size_t count, loff_t *f_pos)
 
 - {
 
 -     pr_info("read requested for %zu bytes \n", count);
 
 -     if((*f_pos + count) > DEV_MEM_SIZE)
 
 -     {
 
 -         count = DEV_MEM_SIZE - *f_pos;
 
 -     }
 
 -     if(copy_to_user(buff, &device_buffer[*f_pos], count))
 
 -     {
 
 -         return -EFAULT;
 
 -     }
 
 -     pr_info("Number of bytes successfully read = %zu\n", count);
 
 -     return count;
 
 - }
 
  复制代码 
 
  
模块加载及卸载代码  
- static int pseudo_chr_dev_release(struct inode *inode, struct file *filp)
 
 - {
 
 -     pr_info("close was successful\n");
 
 -     return 0;
 
 - }
 
  
 
- struct file_operations device_fops={
 
 -     .owner = THIS_MODULE,
 
 -     .open = pseudo_chr_dev_open,
 
 -     .release = pseudo_chr_dev_release,
 
 -     .read = pseudo_chr_dev_read,
 
 -     .write = pseudo_chr_dev_write,
 
 - };
 
  
- dev_t device_number;
 
  
- struct cdev chr_dev;
 
 - struct class *pseudo_class;
 
 - struct device *pseudo_char_device;
 
  
- static int __init pseudo_chrdev_init(void)
 
 - {
 
 -     int ret = 0;
 
 -     ret = alloc_chrdev_region(&device_number, 0, 1, "pseudo_chr_dev");
 
 -     if(ret < 0)
 
 -     {
 
 -         goto fail_dev_num;
 
 -     }
 
 -     pr_info("Device number <major>:<minor> = %d:%d", MAJOR(device_number),MINOR(device_number));
 
  
-     cdev_init(&chr_dev, &device_fops);
 
 -     chr_dev.owner = THIS_MODULE;
 
 -     cdev_add(&chr_dev, device_number, 1);
 
 -     pseudo_class = class_create(THIS_MODULE, "pseudo_class");
 
 -     pseudo_char_device = device_create(pseudo_class, NULL, device_number, NULL,"pseudo_chrdev");
 
 -     pr_info("Module init was successful\n");
 
 -     return 0;
 
 - fail_dev_num:
 
 -     return ret;
 
 - }
 
  
- static void __exit pseudo_chrdev_exit(void)
 
 - {
 
 -     device_destroy(pseudo_class, device_number);
 
 -     class_destroy(pseudo_class);
 
 -     cdev_del(&chr_dev);
 
 -     unregister_chrdev_region(device_number, 1);
 
 -     pr_info("module unloaded\n");
 
 - }
 
 - module_init(pseudo_chrdev_init);
 
 - module_exit(pseudo_chrdev_exit);
 
  
- MODULE_LICENSE("GPL");
 
 - MODULE_AUTHOR("Andyqi");
 
 - MODULE_DESCRIPTION("This is pseudo driver module of character device");
 
  复制代码 
 
内核态测试代码已经ok,我们编写makefile,KERN_DIR 指定内核代码路径,将驱动程序编译为模块。 
- KERN_DIR = /home/rlk/ym625x/myd-ym62x-bsp/myir-ti-linux
 
  
- all:
 
 -         make -C $(KERN_DIR) M=`pwd` modules 
 
  
- clean:
 
 -         make -C $(KERN_DIR) M=`pwd` modules clean
 
 -         rm -rf modules.order
 
  
- obj-m        += char_drv.o
 
  复制代码 
执行make 命令编译,编译通过生成char_drv.ko 文件 
 
 
 
 
2.命令行读取验证 
 
我们已经编译好了ko 文件,模块加载函数内部包含了在文件系统中创建class 并在class 创建设备我们可以通过文件节点来访问驱动程序,对应的节点名称规则如下: 
 
 
 
将编译好的ko 文件上传到开发板通过insmode 命令加载至内核,通过log可知模块加载成功分配了239:0的设备号 
 
 
 
通过cat /sys/class/pseudo_class/pseudo_chrdev/dev 也可以读取到device 对应的设备号为239:0  
 
 
先尝试使?echo命令写入数据到字符设备/dev/pseudo_chrdev   echo "Hello world!" > /dev/pseudo_chrdev,以下log 可以看出我们的write 函数及open函数已经被调用,从上?的命令可以看到整个文件操作的过程,从open到close。 
 
 
我们尝试通过cat 读取文件 cat /dev/pseudo_chrdev ,我们可以看到read 函数已经被调用并且读到了我们写入的"Hello world!"  
 
 
 
3.App程序验证 
 
通过echo,cat 已经验证了文件的基本操作,我们编写如下测试代码在app 空间访问字符驱动程序 
- #include <sys/types.h>
 
 - #include <sys/stat.h>
 
 - #include <fcntl.h>
 
 - #include <unistd.h>
 
 - #include <errno.h>
 
 - #include <stdio.h>
 
 - #include <stdlib.h>
 
 - #define MAX 1048
 
 - char buffer[MAX];
 
 - int main(int argc, char **argv)
 
 - {
 
 -     int fd = 0;
 
 -     int ret = 0;
 
 -     int count = 0;
 
 -     /* 两个参数 */
 
 -     if(argc != 2)
 
 -     {
 
 -         printf("Wrong usage, Please try the way: <file> <number toread>\n");
 
 -         goto exit;
 
 -     }
 
 -     /* 字符转换整数, main函数的参数 */
 
 -     count = atoi(argv[1]);
 
 -     /* 打开字符设备文件 */
 
 -     fd = open("/dev/pseudo_chrdev", O_RDWR);
 
 -     if(fd < 0)
 
 -     {
 
 -         perror("open fail");
 
 -         goto exit;
 
 -     }
 
 -     printf("Open operation was successful\n");
 
 -     /* 从字符设备缓冲读取数据 */
 
 -     ret = read(fd, &buffer[MAX], count);
 
 -     if(!ret)
 
 -     {
 
 -         printf("read failure or end of file\n");
 
 -         goto exit;
 
 -     }
 
 -     else
 
 -     {
 
 -         printf("read %d bytes data from pseudo character device\n", ret);
 
 -     }
 
 -     /* 从用户空间写数据到字符设备 */
 
 -     ret = write(fd, &buffer[MAX], count);
 
 -     if(!ret)
 
 -     {
 
 -         printf("write failure or character device full\n");
 
 -         goto exit;
 
 -     }
 
 -     else
 
 -     {
 
 -         printf("write %d bytes date to pseudo character device\n", ret);
 
 -     }
 
 -     return 0;
 
 - exit:
 
 -     close(fd);
 
 -     return 0;
 
 - }
 
  复制代码 将测试程序编译通过后,传送至开发板运行结果如下,可以看出内核态的open read write 也已经被调用到了 
 
 
 |