字元裝置驅動----LED驅動程式
一. 概念介紹
一般使用者在應用程式裡呼叫的 open, read, write 函式是 c 庫的函式,
這些函式會觸發 swi val異常,從而引發系統呼叫,進入到核心空間,
核心通過VFS(virtual Filesystem)來實現呼叫不同的驅動函式。
例如:我們有一個函式,
int main()
{
int fd1, fd2;
int val = 1;
fd1 = open("/dev/led", O_RDWR);
write(fd1, &val, 4);
fd2 = open("hello.txt", O_RDWR);
write(fd2, &val , 4);
}
函式裡相同的open、write函式,引發的不同的行為,一個是操控硬體,一個是寫檔案。
簡單的呼叫關係如下:
使用者 –> 系統呼叫 –> 驅動程式
open –> sys.open –> led.open
write –> sys.write –> led.write
二. 字元裝置驅動框架
實現步驟:
1. 實現驅動的 led.open, led.write, led.read 操作
2. 定義file_operations結構體, 把驅動函式填充到裡面
3. 把這個結構告訴核心, 通個函式 register_chrdev(major, “first_drv”, &first_drv_fops) 來實現
4. 誰來呼叫註冊函式 –>驅動的入口函式來呼叫這個註冊函式, first_drv_init
5. 修飾一下這個函式入口函式,module_init(first_drv_init)
//第一步:驅動功能實現
static int first_drv_open(struct inode *inode, struct file *file)
{
//printk("first_drv_open\n");
return 0;
}
static ssize_t first_drv_write(struct file *file,
const char __user *buf,
size_t count,
loff_t * ppos)
{
//printk("first_drv_write\n");
return 0;
}
//第二步:定義結構體,並把驅動函式填充進去
static struct file_operations first_drv_fops = {
.owner = THIS_MODULE,
.open = first_drv_open,
.write = first_drv_write,
};
//第四步:實現驅動入口函式
int major;
static int first_drv_init(void)
{
//第三步:把結構體告訴核心
major = register_chrdev(0, "first_drv", &first_drv_fops);// 註冊告訴核心
return 0;
}
static void first_drv_exit(void)
{
unregister_chrdev(major, "first_drv"); // 解除安裝
}
//第五步:修飾入口函式,及退出函式
module_init(first_drv_init);
module_exit(first_drv_exit);
三. 關聯 [裝置號] 與 [裝置節點]
裝置號要與裝置結點關聯起來,才能通過open(“/dev/xyz”)方便的操作。
1. 設定主裝置號
驅動程式可以自動分配主裝置號, 也可以手工指定
// 設定為 0 時是系統自動分配主裝置號
major = register_chrdev(0, "first_drv", &first_drv_fops);
// 通過 [cat /proc/device] 看一下系統為我們的first_drv分配的裝置號是多少
// 手動分配 111主裝置號給 first_drv
register_chrdev(111, "first_drv", &first_drv_fops);
2. 設定裝置節點
當應該程式 執行 open(“/dev/xyz”) 操作時,這個/dev/xyz怎麼來的
2.1 手動建立
// 建立裝置節點
mknod /dev/xyz c(表示是字元裝置) 主裝置號 次裝置號
//檢視裝置資訊:
ls -l /dev/xyz
2.2 自動建立
mdev – 根據系統資訊建立裝置節點
int major;
static int first_drv_init(void)
{
major = register_chrdev(0, "first_drv", &first_drv_fops);
//建立裝置資訊,執行後會出現 /sys/class/firstdrv
firstdrv_class = class_create(THIS_MODULE, "firstdrv");
//建立裝置節點,就是根據上面的裝置資訊來的
firstdrv_class_dev = class_device_create(firstdrv_class,
NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */
return 0;
}
static void first_drv_exit(void)
{
unregister_chrdev(major, "first_drv");
//刪除節點及資訊
class_device_unregister(firstdrv_class_dev);
class_destroy(firstdrv_class);
}
四. 完善LED驅動
完善LED驅動,也就是硬體的操作。
微控制器中可以直接操作實體地址,但在驅動裡只能操作虛擬地址
虛擬地址怎麼來的,用 ioremap( ) 函式來對映,對映完後操作虛擬地址就像操作實體地址一樣。
static int first_drv_init(void)
{
major = register_chrdev(0, "first_drv", &first_drv_fops);
firstdrv_class = class_create(THIS_MODULE, "firstdrv");
firstdrv_class_dev = class_device_create(firstdrv_class,
NULL, MKDEV(major, 0), NULL, "xyz");
//對映 GPIO 的實體地址 0x56000050 到虛擬地址, gpfcon操作的是虛擬地址
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;
return 0;
}
對映完後,就可以操作這些地址,來控制硬體暫存器
static int first_drv_open(struct inode *inode, struct file *file)
{
//printk("first_drv_open\n");
/* 配製GPF4為輸出 */
*gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)));
*gpfcon |= ((0x1<<(4*2)) | (0x1<<(5*2)) | (0x1<<(6*2)));
return 0;
}
另外,當用戶空間的資料要傳到核心空間裡,同樣不能直接用,
也要通過 copy_from_user() 函式,把使用者空間的值傳到核心空間裡。
static ssize_t first_drv_write(struct file *file,
const char __user *buf,
size_t count, loff_t * ppos)
{
int val;
//printk("first_drv_write\n");
//把使用者空間的值 copy 給核心空間
copy_from_user(&val, buf, count); // copy_to_user();
if (val == 1)
{
// 點燈
*gpfdat &= ~((1<<4) | (1<<5) | (1<<6));
}
else
{
// 滅燈
*gpfdat |= (1<<4) | (1<<5) | (1<<6);
}
return 0;
}