字符设备驱动

来源:互联网 发布:阿里云系统盘迁移 编辑:程序博客网 时间:2024/06/06 07:13
如何一步一步执行驱动:
在进行驱动加载的过程中使用 insmod mydev.ko时,会执行驱动的加载函数,
在驱动的加载函数中会进行设备的注册
1. MKDEV (hello_major, hello_minor)去生成设备号
2. 向内核去注册设备节点result = register_chrdev_region (dev, number_of_devices, "hello");
3. 创建一个file_operations hello_fops 结构体,并且对这个结构体初始化,
   这个结构体是实现驱动本身的open read write close的功能
4. 使用cdev_init函数进行初始化 cdev_init(&my_cdev, &fops);让my_dev和fops进行绑定, cdev->ops = fops;
cdev结构体定义如下:
struct cdev {
    struct kobject kobj;
    struct module *owner;
    const struct file_operations *ops;
    struct list_head list;
    dev_t dev;
    unsigned int count;
};

5. 使用cdev_add()把这个my_cdev这个结构体加入到cdev链表当中, 驱动加载完成

6. 驱动加载后会,会有对应的设备节点,此时使用mknod命令来创建设备号
   mknod /dev/mydev c 250 0

7. 执行mknod命令后,时会在内核中创建一个inode的结构体,这里只写inode重要的成员信息
    struct inode {
  dev_t i_rdev;        //该成员表示设备文件的inode结构,它包含了真正的设备编号。
  struct cdev *i_cdev; //该成员表示字符设备的内核的内部结构。当inode指向一个字符设备文件时,
                       //该成员包含了指向struct cdev结构的指针,其中cdev结构是字符设备结构体。
    };
    首先会对i_rdev 这个成员赋值,会对成员赋值为相应的设备号。i_rdev = (250,0);
    其次会对i_cdev 赋值,通过设备号在cdev链表中查找,可以找到我们通过cdev_add()添加的结构体,
    故 i_cdev = &my_cdev
8. 应用程序调用 open("/dev/hello")这个函数时,内核会调用sys_open, 内核会在调用open函数时创建struct file 结构体,
   并且会对file结构体的成员f_op赋值为驱动中的file_operation结构体(通过设备号可以找到可以找到inode结构体,
   inode结构体中有i_cdev指针,指向系统的cdev的结构体,cdev的成员ops指向驱动的file_operation成员hello_ops)
   应用程序调用open,驱动中也会调用file_operation的open函数,故系统正确的调用.
   应用程序调用open后,对于驱动来说,filep->f_op = hello_ops,最终会调用驱动的open函数

struct file {
 struct path f_path;
 const struct file_operations *f_op;
 spinlock_t f_lock; /* f_ep_links, f_flags, no IRQ */
 atomic_long_t f_count;
};


1. 设备号: 包括主设备号和次设备号
  (1) 主设备号: 表示是哪一类的驱动 usb adc led,uart
  (2) 通过 cat /proc/devices 显示系统设备号
  (3) 字符设备和块设备的设备号是独立的
  (4) 次设备号 用来表示这一类设备中的哪一个设备
  (5) 设备号内核用一个变量来表示 dev_t(ulong)来表示一个设备号
  (6) 高20为来表示主设备号 低12为表示次设备号
      major = 0 ~ 1M
      minor = 0 ~ 4096
  (7) 可以使用一个宏函数去生成一个设备号
      #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
      dev_t devno = MKDEV(250,0)

2. 设备的申请:
(1)静态申请设备号  
int register_chrdev_region(dev_t from, unsigned count, const char *name)
依赖的头文件: #include<linux/fs.h> 
form  : 从哪一个设备号开始向内核去申请设备号,向内核去申请一个或多个设备号,
        如果这个设备号内核没有占用,可以申请到,否则申请失败 
count : 表示要申请设备的个数 
name  : 要申请设备的名称
returned: 成功返回 ;失败 负数  
实例:
 int ret;
    dev_t devno = MKDEV(major,minor);
    ret = register_chrdev_region(devno,1,"char");
    if(ret < 0 )
    {
        printk("fail to get devno\n");
        return ret;
    }
使用命令去查看申请到的设备号 cat /proc/devices
(2)动态申请设备号 
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) 
依赖的头文件: #include<linux/fs.h>
dev : 设备号变量的指针
baseminor : 次设备号开始于那一个数字
count : 需要的多少个设备
name : 要申请设备的名称
returned: 成功返回 0 失败 负数

3.设备号的注销
void unregister_chrdev_region(dev_t from, unsigned count)
依赖的头文件: #include<linux/fs.h>
form : 要释放的设备号
count : 要释放几个设备号

4. 字符设备(cdev)注册:
依赖头文件 linux/cdev.h
  (1)struct cdev{
     struct kobject kobj;       //内嵌的kobject对象
     struct module *owner;      //所属模块
     const struct file_operations *ops;    //文件操作结构体
     struct list_head list;
     dev_t dev;         //设备号
     unsigned int count;
  };
 (2) cdev_init() 初始化一个cdev结构体,把cdev这个结构体和file_operations结构体进行关联
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
cdev: 要初始化的结构体指针
fops : 自定义实现的file_operations结构体指针
 (3)file_operations是这个字符设备驱动所支持的操作
struct file_operations {
     struct module *owner;
     loff_t (*llseek) (struct file *, loff_t, int);
     ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
     ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
     ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
     ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
     int (*readdir) (struct file *, void *, filldir_t);
     unsigned int (*poll) (struct file *, struct poll_table_struct *);
     long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
     long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
     int (*mmap) (struct file *, struct vm_area_struct *);
     int (*open) (struct inode *, struct file *);
     int (*flush) (struct file *, fl_owner_t id);
     int (*release) (struct inode *, struct file *);
     int (*fsync) (struct file *, loff_t, loff_t, int datasync);
     int (*aio_fsync) (struct kiocb *, int datasync);
     int (*fasync) (int, struct file *, int);
     int (*lock) (struct file *, int, struct file_lock *);
     ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
     unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
     int (*check_flags)(int);
     int (*flock) (struct file *, int, struct file_lock *);
     ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
     ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
     int (*setlease)(struct file *, long, struct file_lock **);
     long (*fallocate)(struct file *file, int mode, loff_t offset,
     loff_t len);
};
(4) cdev.owner = THIS_MODULE; 表示的是指向本模块的指针   #define THIS_MODULE (&__this_module) 
(5) cdev_add() 向系统添加一个字符设备
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
/*
 * cdev_add() - add a char device to the system 
 * @p: the cdev structure for the device
 * @dev: the first device number for which this device is responsible
 * @count: the number of consecutive minor numbers corresponding to this device 
 * cdev_add() adds the device represented by @p to the system, making it
 * live immediately. A negative error code is returned on failure.
 */
(6) cdev_del()从系统链表中删除一个字符设备
/**
 * cdev_del() - remove a cdev from the system
 * @p: the cdev structure to be removed
 * cdev_del() removes @p from the system, possibly freeing the structure
 * itself.
 */
void cdev_del(struct cdev *p)
{
    cdev_unmap(p->dev, p->count);
    kobject_put(&p->kobj); 
}

(7)创建一个设备文件 mknod /dev/mychar c 250 0
 crw-r--r-- 1 root root 250, 0 7月 30 16:46 /dev/mychar

(8)定义一个io命令
   #define LEDON  _IO('A',0)
   #define LEDOFF _IO('A',1)
依赖一个头文件: #include<asm/ioctl.h>

(9)ioremap : 用来将I/O内存资源的物理地址映射到核心虚拟地址空间(3GB-4GB)中
    gpc0con = ioremap(GPC0CON, 4);
    gpc0dat = ioremap(GPC0DAT, 4);
    头文件: #include <asm/io.h>
    writel((readl(gpc0con) & ~(0xff<<12)) | (0x11<<12), gpc0con);
    writel((readl(gpc0dat) & ~(0x3 << 3)), gpc0dat);
    writel((readl(gpc0dat) | (0x3 << 3)), gpc0dat);
在内核驱动程序的初始化阶段,通过ioremap()将物理地址映射到内核虚拟空间;在驱动程序的mmap系统调用中,使用remap_page_range()将该块ROM映射到用户虚拟空间。这样内核空间和用户空间都能访问这段被映射后的虚拟地址

(10), 驱动中和应用程序中关于ioctl的命令定义格式
#define _IO(type,nr)        _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size)  _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW(type,nr,size)  _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))

#define LEDON  _IO('A',0)
#define LEDOFF _IO('A',1)
#define LEDSET _IOW('A',2,1)
type : 表示类型,是命令的类别(组),这个数的表示范围是0x0 ~ 0xff led控制
 nr  : 这一组命令的第几个      led控制的方式, on off
_IOR : 功过ioctl要读 

(11) 如果insmod char.ko 出现没有这个目录时, (rmmod: char(3.2.0): No such file or directory)
     解决办法: mkdir -p /lib/modules/$(uname -r)

(12) 把内核空间里面的数据传递给用户空间 copy_to_user 
     依赖头文件: #include<asm/uaccess.h>
extern inline long copy_to_user(void __user *to, const void *from, long n) 
     return __copy_tofrom_user((__force void *)to, from, n, to);
}
参数:
to : 数据的目的
from: 数据的源
n : 数据的大小

如果数据拷贝成功,则返回零;否则,返回没有拷贝成功的数据字节数。

   
(13) 把用户空间内的值读到内核空间中 copy_from_user
extern inline long copy_from_user(void *to, const void __user *from, long n)
{
    return __copy_tofrom_user(to, (__force void *)from, n, from);
}
to  : 数据的目的 ,要写入内核空间的buf内
from: 数据的源 ,来至于用户空间的,
n   : 用户空间要向内核空间写入的数据大小
失败返回没有被拷贝的字节数,成功返回0.
num = copy_from_user(buf,from,count);
if(num < 0 )
{
    printk("copy_to_user is failed\n");
    return ret;
}

(14) ssize_t (*read) (struct file *filep, char __user *to , size_t count , loff_t * off);
filep : 指向内核创建的file结构体指针
to    : 要向用户空间发送数据的指针,
count : 用户空间要读取的字节数
off   : 文件指针的偏移量

(15) ssize_t (*write) (struct file *, const char __user *from, size_t count, loff_t *off);
filep : 指向内核创建的file结构体指针
from : 要接受用户空间发给内核空间数据的指针
count : 用户空间要写的字节数
off : 文件指针的偏移量


0 0