字符設備驅動
1.驅動中如何描述一個對象
由結構體描述
struct cdev { struct kobject kobj; //kobject kset 文件系統 ---sysfs文件系統 struct module *owner; //THIS_MODULE const struct file_operations *ops; //操作方法集 struct list_head list; //鏈表,用來管理字符設備 dev_t dev; //設備號 typedef unsigned long u_long; 32位 ------主設備號 + 次設備號unsigned int count; // //隸屬於同一主設備號的次設備號的個數 12 20 };
應用層和驅動層的邏輯關系如圖
在驅動中通過函數指針實現對硬件的操作
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 *); //寫 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //ioctl long (*compat_ioctl) (struct file *, unsigned int, unsigned long); //向下兼容linux內核 int (*mmap) (struct file *, structvm_area_struct *); //顯示屏 - 音視頻驅動的比較多 int (*open) (struct inode *, struct file *); //打開 int (*release) (struct inode *, struct file *); //關閉 }; const struct file_operations *ops; //操作方法集
字符設備的定義和初始化:
1.定義一個字符設備 struct cdev* cdev;
2.結構體的指針並沒有實際分配內存,所以需要對cdev分配內存,在驅動中使用kzalloc或者直接調用cdev_alloc分配內存空間
3.初始化字符設備void cdev_init(struct cdev *cdev, const struct file_operations *fops),第二個參數可自己定義,結構體中的方法可不全部使用,使用.xxx的方式來使用
設備號的獲取:
在linux中設備號是唯一用來區分設備是幹什麽的,相當於設備的身份證,設備好號分為主設備號和次設備號,主設備號表示一類設備,次設備號表示某個設備
在驅動中使用dev_t dev 獲取主設備號使用 MAJOR(dev) 獲取次設備號MINOR(dev) 制作設備號使用MKDEV(ma,mi)
設備號的動態註冊(系統分配)
---------寫代碼的時候,以框架為主題,然後框架當中你的接口需要什麽類型,你就給他什麽類型。
有註冊一定要有註銷
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name) 函數的功能;動態獲取設備號 參數: @dev : 設備號:函數返回之後會將設備號保存到dev中 @baseminor :最小的次設備號的基地址 0 @count :設備的個數 3 @name :自己填充,最好見名知意 返回值:成功返回0 失敗返回錯誤碼
設備號的靜態註冊(自己指定)
例子
靜態註冊設備號: int major = 249; MKDEV(major,0); int register_chrdev_region(dev_t from, unsigned count, const char *name) 函數的功能;靜態獲取設備號 參數: @from : 指定的主設備號: @count : 設備的個數 3 @name :自己填充,最好見名知意 返回值:成功返回0 失敗返回錯誤碼
設備號的註銷:
void unregister_chrdev_region(dev_t from, unsigned count) 功能:註銷設備號 參數: * @from: 被註銷的第一個設備值,就是你指定的主設備號的值 * @count: 設備數量
添加一個字符設備到系統當中去:
添加進系統是我們最初的夢想,現在想來cdev_add需要 cdev我們定義了cdev--》分配內存--》cdev初始化 ,第二個參數需要dev_t 我們動態/靜態註冊設備號
int cdev_add(struct cdev *p, dev_t dev, unsigned count) 函數的功能;添加一個字符設備到系統當中去 參數: @p :字符設備 @dev :設備號 @count :個數 返回值:失敗返回負值 成功返回0
貼上全部的代碼
#include <linux/module.h> #include <linux/moduleparam.h> #include <linux/fs.h> #include <linux/cdev.h> //定義一個字符設備對象 struct cdev *cdev; dev_t dev; unsigned int baseminor = 0; unsigned int count = 1; const char * name = "demochar"; //向上層提供的接口 //向下層操作硬件----在接口裏去操作硬件 const struct file_operations fops = { }; static int __init demo_init(void) { int retval; printk("----%s---%d.\n",__func__,__LINE__); //1、為字符設備分配空間 cdev = cdev_alloc(); if(cdev == NULL){ printk("cdev_alloc faild.\n"); return -ENOMEM; } //2、初始化字符設備 cdev_init(cdev,&fops); //3、動態申請設備號 retval = alloc_chrdev_region(&dev, baseminor, count, name); if(retval){ printk("alloc_chrdev_region faild.\n"); goto err0; } printk("major :%d.\n",MAJOR(dev)); //4、添加到內核 retval = cdev_add(cdev, dev, count); if(retval){ printk("cdev_add failed.\n"); goto err1; } //5、硬件初始化 return 0; err1: unregister_chrdev_region(dev, count); err0: return retval; } static void __exit demo_exit(void) { printk("----%s---%d.\n",__func__,__LINE__); cdev_del(cdev); unregister_chrdev_region(dev, count); } //入口函數 module_init(demo_init); //出口函數 module_exit(demo_exit); //GPL許可協議 MODULE_LICENSE("GPL");
重點來了 register_chrdev
我們上面說的註冊流程有點啰嗦,聰明的人,將其封裝成一個接口供我們使用,但是我們知其然還要知道其所以然否則以後出錯都不知道怎麽debug
register_chrdev(unsigned int major, const char *name, const struct file_operations *fops) 功能: 創建並註冊一個字符設備 參數: major :主設備號 major == 0; 動態分配 0 < major < 254 ;靜態分配 name :設備的名字 fops :操作方法集 返回值: * If @major == 0 返回主設備號major * If @major > 0 成功返回0 失敗返回錯誤碼
13、釋放設備號:
static inline void unregister_chrdev(unsigned int major, const char *name)
@major :主設備號
@name :名字
```
太簡短了。。。
#include <linux/module.h> #include <linux/moduleparam.h> #include <linux/fs.h> #include <linux/cdev.h> //定義一個字符設備對象 struct cdev *cdev; unsigned int major = 0; const char * name = "demochar"; //向上層提供的接口 //向下層操作硬件----在接口裏去操作硬件 const struct file_operations fops = { }; static int __init demo_init(void) { printk("----%s---%d.\n",__func__,__LINE__); major = register_chrdev(0, name, &fops); if(major <= 0){ printk("register_chrdev failed.\n"); return major; } printk("major :%d.\n",major); //5、硬件初始化 return 0; } static void __exit demo_exit(void) { printk("----%s---%d.\n",__func__,__LINE__); unregister_chrdev(major, name); } //入口函數 module_init(demo_init); //出口函數 module_exit(demo_exit); //GPL許可協議 MODULE_LICENSE("GPL");
字符設備驅動