字符設備驅動程序設計
概念
字符設備文件:
- 應用程序通過字符設備文件而調用字符設備驅動的文件操作
- 映射關係是一個文件描述符
字符設備文件創建方法可以使用以下命令形式:
cat /proc/devices // 查看主設備號
mknod /dev/xxx c 100 3 //(指明是字符設備) 主設備號 次設備號
或者直接用使用函數註冊,自動生成。
設備號
數據類型:dev_t
高十二位爲主設備號
低二十位爲次設備號
主設備號:
標識設備對應的驅動程序
每個在內核中活動的字符設備驅動程序,包括編譯進內核的和後來動態加載的,都有唯一的主設備號。
內核利用主設備號將設備與相應的驅動程序對應起來
- 次設備號
只由驅動程序使用,內核的其他部分不使用它,僅將它傳遞給驅動程序
設備號操作
a) 設備號合成分解
dev_t dev = MKDEV(主設備號,次設備號);/*主次設備號自己給,已知*/
主設備號= MAJOR(dev_t dev) /*設備號分解*/
次設備號= MINOR(dev_t dev)
b) 設備號處理(驅動加載時都需要申請相關設備號)
register_chrdev_region() // 靜態申請
alloc_chrdev_region()//動態申請
unregister_chrdev_region()//設備號註銷
設備的抽象封裝
字符設備結構體:
struct cdev
{
struct kobject kobj; /*內核使用,驅動可以不用處理*/
struct module *owner; /*模塊擁有者*/
const struct file_operations *ops; /*模塊的文件操作*/
struct list_head list; /*內核鏈表,驅動可以不處理*/
dev_t dev; /*設備號*/
unsigned int count; /*設備數量*/
};
字符設備操作函數
i. 結構分配:
1. 靜態分配:struct cdev xxx_dev;
2. 動態分配:struct cdev* pdev = cdev_alloc();
ii. 結構初始化:
void cdev_init(struct cdev *, const struct file_operations *);
iii. 字符設備註冊:
int cdev_add(struct cdev *, dev_t dev_num, unsigned count);
iv. 字符設備刪除
void cdev_del(struct cdev *p)
4.字符設備文件操作封裝:
a) 上層應用程序使用文件操作函數都是通過該結構體映射到驅動中的文件操作中
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);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
.....
. ....
};
b) 文件操作相關結構體解析
struct inode :
每個存放在文件系統裏的文件都會關聯一個 inode結構體,該結構主要用來記錄文件物理上的信息
一個文件沒有被打開時不會關聯 file結構,但會關聯一個inode結構
struct file :
設備驅動所使用的一個最重要的數據結構
file 代表一個打開的文件,內核在open()時創建且在close()前作爲參數傳遞給操作在設備上的函數
驅動程序可能會用到的結構成員(其他大部分成員由內核操作,驅動不關心):
fmode_t f_mode; //驅動程序的ioctl()可能需要查看文件的權限
loff_t f_pos; //當前讀寫位置
unsigned int f_flags;//文件標誌,
//爲了支持非阻塞型操作,驅動程序許需要檢查該標誌
const struct file_operations *f_op; //與文件對應得操作。
i. 內核在完成open()時對該指針賦值,以後需要分派操作時就讀這些數據。
ii. filp->f_op 中的值從不保存供給以後應用;即需要修改文件所對應的操作,下次再操作那個文件的相應操作時就會調用新方法
void *private_data;
i. 系統調用驅動程序的open()方法前將這個指針設置爲NULL
ii. 驅動程序可以用該字段指向已分配的數據,但要在內核釋放file結構前的release()方法中清除它
參數傳遞是內核傳遞的,具體是怎麼傳遞,這是操作系統的事。有興趣看內核的實現
文件操作函數解析
int open(struct inode *inode,struct file *filp);// 打開設備文件
i. 一般用於對設備進行初始化
ii. 最重要的調用時MOD_INC_USE_COUNT宏,該宏主要用來增加驅動程序使用計數器,以避免不正確的卸載驅動程序
iii. 參數說明:
inode:打開文件所對應的i節點,對驅動程序來說,這個參數的主要作用就是獲取從設備號
file:一個打開的文件,內核分配
iv. 代碼架構
static int open(struct inode *inode,struct file *filp)
{
從設備號 = MIMOR(inode->i_rdev);
通過設備號判斷需要操作的設備;
if(需要操作的設備使用計數器== 0)
{
初始化設備(包括資源申請);
給filp->private_data賦值,以標識物理設備
}
需要操作的設備的使用計數器++;
return 0;
}
int release(struct inode *inode,struct file *filp);//d) 釋放設備
i. 當應用程序不再使用時釋放
ii. 最重要的宏:MOD_DEC_USE_COUNT,主要用來減少驅動程序使用計數器
iii. 一般還用於在驅動程序支持的設備使用完畢時,對驅動程序支持設備進行關閉操作
iv. 代碼架構
static int release(struct inode* inode,struct file *filep)
{
從設備號 = MINOR(inode->i_rdev);
通過從設備號判斷需要操作的設備;
MOD_DEC_USE_COUNT;
需要操作的設備的使用計數器--;
if(需要操作的設備的使用計數器== 0)
{
關閉設備處理(包括釋放revalidate()方法獲取的資源);
}
return 0;
}
ssize_t read(struct file *filep, char *buf, size_t count, loff_t *fpos); //e) 文件讀取
i. 參數說明:
filep:代表一個“打開的文件”,它由內核在open()時創建且在close()前當做參數傳遞給操作在設備上的函數,在文件關閉後,內核釋放這個數據結構
buf:從設備讀取的數據將保存到它指向的內存空間
count:指明將要讀取的數據個數
fpos:文本指針
ii. 代碼架構:
static ssize_t read(struct file* filp ,char *buf, size_t count, loft_t *f_pos)
{
if(!access_ok(VERIFY_WRITE,(void *)buf,count))
{
return -EFAULT;
}
通過filp->private_data確定實際操作的物理設備
從設備的*f_ops位置讀取count個字節數
使用copy_to_user()或者put_user()拷貝數據到buf指向的空間;把數據從內核空間拷貝到用戶空間
更改 *f_ops;
return 讀到的數據總數;
}
ssize_t write(struct file *filep, char *buf, size_t count, loff_t *fpos);//f) 寫文件
i. 代碼架構
static ssize_t write(struct file* filp ,char *buf, size_t count, loft_t *f_pos)
{
if(!access_ok(VERIFY_READ,(void *)buf,count)) /*檢查程序是否能以指定的方式 訪問指定地址指定長度的內存*/
{
return -EFAULT;
}
通過filp->private_data確定實際操作的物理設備
使用copy_form_user()或者get_user()從buf指向的空間獲取數據;把數據從用戶空間拷貝到內核空間
從設備的*f_ops位置寫入獲取的數據
更行 *f_ops;
return 寫入的數據總數;
}
loff_t llseek(struct file *filp,loff_t off, int whence);//g) 修改文件讀寫位置
i. 參數說明
filp:文件指針
off:需要移動的偏移量
whence:移動方式
a) 0:文件頭移動
b) 1:當前位置移動
c) 2:從文件尾開始移動
static loff_t llseek(struct file *filp,loff_t off, int whence)
{
loff_t newpos;
通過filp->private_data確定實際操作的物理設備
獲取文件長度;
switch(whence)
{
case 0:
newpos = off;
break;
case 1:
newpos = filp->f_pos + off;
break;
case 2:
newpos = 文件長度+ off;
break;
default:
return -EINVAL;
}
if(newpos < 0)
{
return -EINVAL;
}
filp->f_pos = newpos;
return newpos;
}
5.驅動到應用的數據流實現
a) 從用戶空間讀取數據:copy_from_user(to,from,n)
int _copy_from_user(void *to, const void __user *from, unsigned long n)
b) 將數據上報到用戶空間:copy_to_user(to,from,n)
int copy_to_user(void __user *to, const void *from, int n)
6.內核空間與I/O空間的數據交換
a) LINUX使用的內存地址都是虛擬地址
b) 要對實際的物理地址進行操作,需要使用使用IO映射函數之後才能對相應的物理地址進行操作
c) 物理寄存器操作過程:
i. 申請映射物理地址
ioremap(unsigned long phys_addr, size_t size)/*成功返回虛擬地址的指針*/
ii. IO讀寫,對返回的虛擬地址進行操作:
- 先讀回當前硬件寄存器的值
a) ior32(reg)
b) ior16(reg)
c) ior8(reg)
- 在讀回來的基礎上運算,重新設定後再寫入
a) iow32(reg, val)
b) iow16(reg, val)
c) iow8(reg, val)
字符設備驅動程序設計框架
1.驅動初始化
a) 分配cdev結構體
i. 既使用靜態方法;定義一個結構體就好了
ii. 動態分配,使用 cdev_alloc();
b) 申請設備號
iii. alloc_chrdev_region()
c) 初始化cdev
i. cdev_init();
1. 這裏可以創建類別 class_create()
2. 加入類創建設備文件
a) device_create();
d) 註冊cdev
i. cdev_add();
e) 硬件初始化
2.填充設備操作
a) 初始化 file_operations
b) 編寫操作函數
3.驅動註銷
a) 註銷設備號 unregister_chrdev_region();
b) 註銷字符設備
i. device_destroy()
ii. release_region()
iii. cdev_del
iv. kfree()如果有申請內存的話
c) 銷燬類(如果有創建類)
i. class_destroy();
LED驅動程序設計
/**********************************************
作者:hntea
時間:2016/3/8
功能:TQ210 led字符設備驅動,程序的結構還可以再優化
**********************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fcntl.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#define DEV_NAME "led"
#define MAJOR_NR 10
#define MINIR_NR 1
#define LED_ON 0x00000018
#define LED_OFF 0X00000000
#define CON_ADDR 0xE0200060
#define DAT_ADDR 0xE0200064
struct cdev led_dev; /*靜態分配設備號*/
dev_t dev_nm; /*設備號*/
unsigned int *led_config; /*設計成全局變量,方便寫函數使用*/
unsigned int *led_data;
/*************************************************
函數名: led_hardinit 實現
函數參數:
函數功能:led硬件初始化
*************************************************/
static void led_hardinit(void)
{
unsigned tmp = 0;
/*找出物理地址對應的虛擬地址*/
led_config = ioremap(CON_ADDR,4); /*該寄存器是32位數值*/
led_data = ioremap(DAT_ADDR,4);
/*讀取當前寄存器狀態*/
tmp = ioread32(led_config);
tmp &= (~(0x11 << 3));
tmp |= (0x11 << 3);
/*寄存器設置寫回*/
iowrite32(tmp,led_config);
iowrite32(LED_ON,led_data); /*初始化成功燈亮提示*/
}
/*************************************************
函數名: file_operations 實現
函數參數:
函數功能:
*************************************************/
static int led_open (struct inode *inode, struct file *fp)
{
int ret = 0;
/*led 打開時只需要初始化相關寄存器就夠*/
led_hardinit();
return ret;
}
static int led_release (struct inode *inode, struct file *fp)
{
return 0;
}
static long led_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
{
return 0;
}
/*************************************************
函數名: led_write 實現
函數參數:
函數功能:向led設備文件寫如數據
*************************************************/
static ssize_t led_write(struct file *fp, const char __user *buf, size_t count, loff_t *f_pos)
{
unsigned tmp = count ; /*獲取寫入字節數*/
unsigned char led_sta[2]; /*用來存放兩個led的狀態*/
int i = 0;
unsigned int ledDataTmp;
memset(led_sta,0,2); /*數據清零*/
if( count > 2)
{
tmp = 2;
}
/*將數據從用戶空間複製到內核空間*/
if(copy_from_user(led_sta,buf,tmp))
{
return -EFAULT;
}else
{
/*控制兩個led的狀態*/
for(i=0;i<2;i++)
{
/*讀取當前led寄存器狀態,嚴謹就要處理,防止數據破話而使系統奔潰*/
ledDataTmp = ioread32(led_data);
if( led_sta[i] == '1' )
{
/*粗略處理,只要有一個是1就全亮*/
iowrite32(LED_ON,led_data);
}else{
iowrite32(LED_OFF,led_data);
}
}
}
return 0;
}
struct file_operations ledfp=
{
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
.unlocked_ioctl = led_ioctl,
.release = led_release,
};
struct cdev cdev={
.owner = THIS_MODULE,
.ops = &ledfp,
};
static int ledInit(void)
{
int ret = 0;
/*1.分配設備號,0是以該數字作爲主設備號分配的起始,2是次設備號(用來標識設備的個數)*/
if((ret = alloc_chrdev_region(&dev_nm, 0, 1,DEV_NAME))<0)
{
printk("device num alloc err!\n");
}
/*2.分配設備結構;靜態分配一個全局變量,打印出來方便手工創建設備文件*/
printk("led major number is %d\n",MAJOR(dev_nm));
printk("led minor number is %d\n",MINOR(dev_nm));
/*3.初始化設備結構*/
cdev_init(&cdev, &ledfp);
/*4.註冊設備*/
if((ret = cdev_add(&cdev,dev_nm, 1)) < 0) /*最後一個參數說明設備數*/
{
printk("cdev add err!\n");
}
/*5.創建設備文件,現在使用 手工創建mknod*/
//device_create();
printk("led.ko insmod success!\n");
return 0;
}
static void ledexit(void)
{
/*2.註銷設備號,註銷設備*/
unregister_chrdev(MAJOR(dev_nm),DEV_NAME);
cdev_del(&cdev);
printk("led device rmmod success!\n");
}
MODULE_AUTHOR("hntea");
MODULE_LICENSE("GPL");
module_init(ledInit);
module_exit(ledexit);