編寫驅動程序,首先要了解是什么類型的設備。linux下的設備分為三類,分別為:字符設備,塊設備和網絡設備。字符設備類型是根據是否以字符流為數據的交換方式,大部分設備都是字符設備,如鍵盤,串口等,塊設備則是以塊為單位進行管理的設備,如,磁盤。網絡設備就是網卡等。
其次要了解應用程序和驅動程序的區別,兩者的主要區別分為以下三點:
1入口函數的任務不相同,應用程序完成一個任務,驅動只完成初始化工作,比如中斷
申請,寄存器設置,定時器設置。
2運行時的cpu模式不相同,驅動具有很高的權限,應用程序是在用戶態下運行,而驅
動程序是在內核態下執行。
3 驅動程序不能調用C庫函數,內核為驅動程序提供一些函數。如printk(KERN_NOTICE fmt, ##arg),第一個參數為打印級別,有如下的打印級別:
KERN_EMERG 用于緊急事件,一般是系統崩潰前的提示信息
KERN_ALERT 用于需要立即采取動作的場合
KERN_CRIT 臨界狀態,通常設計驗證的硬件或軟件操作失敗
KERN_ERR 用于報告錯誤狀態.設備驅動程序通常會用它報告來自硬件的問題
KERN_WARNING 就可能出現的問題提出警告.這些問題通常不會對系統造成嚴重破壞
KERN_NOTICE 有必要提示的正常情況.許多安全相關的情況用這個級別匯報
KERN_INFO 提示性信息.有很多驅動程序在啟動時用這個級別打印相關信息
KERN_DEBUG 用于調試的信息
u_long copy_from_user(void *to, const void *from, u_long len),由用戶態拷貝到內核態;
u_long copy_to_user(void * to, const void *from, u_long len),由內核態拷貝到用戶態。
鑒于以上區別,驅動程序需要完成以下三點基本功能:
1:要對設備進行初始化和釋放功能模塊,就如上面的寄存器設置,中斷的申請,向內核注
冊驅動程序(register_chrdev()),卸載驅動程序(unregister_chrdev())。
2:能進行數據傳輸,在read(),write()函數里具體實現,數據傳輸工作。
3:能進行控制操作,給用戶提供的ioctl()函數里可實現一些用戶的選擇性設置功能。
確定一個設備的執行函數集(結構體)
static struct file_operations myGPIO_fops = {
owner: THIS_MODULE,
write: myGPIO_write,
read: myGPIO_read,
ioctl: myGPIO_ioctl,
open: myGPIO_open,
release: myGPIO_release,
};
接下來是初始化工作,需要寫在一個init()函數中,這個函數是獨立的也是自動執行的,在這之中主要是對一些寄存器進行初始化操作。同樣需要完成卸載驅動模塊。
myGPIO_Major = register_chrdev(0, DRIVER_NAME, &myDriver_fops);
上面的程序完成設備號的注冊,第一個參數為主設備號,一般為0,由系統來分配。
第二個參數為設備名,這需要在/dev/(/dev目錄下設備名由命令 <mknod 設備名 C 主設備號 從設備號>來生成)目錄下出現的設備名相符合。相反的在卸載中就取消注冊
unregister_chrdev(myGPIO_Major, DRIVER_NAME);
最后將這兩個模塊加入到內核中,由程序段的最后兩行完成。
static int __init myGPIO_init(void)
{
PRINTK("GPIO init\n");
myGPIO_Major = register_chrdev(0, DRIVER_NAME, &myGPIO_fops);
if(myGPIO_Major < 0)
{
PRINTK("register char device fail!\n");
return myGPIO_Major;
}
PRINTK("register myGPIO OK! Major = %d\n", myGPIO_Major);
#ifdef CONFIG_DEVFS_FS
devfs_myDriver_dir = devfs_mk_dir(NULL, "GPIO", NULL);
devfs_myDriver_raw = devfs_register(devfs_myDriver_dir, "raw0", DEVFS_FL_DEFAULT, myGPIO_Major, 0, S_IFCHR | S_IRUSR | S_IWUSR, &myGPIO_fops, NULL);
PRINTK("add dev file to devfs OK!\n");
#endif
return 0;
}
static void __exit myGPIO_exit(void)
{
/* Module exit code */
PRINTK("GPIO exit\n");
/* Driver unregister */
if(myGPIO_Major > 0)
{
#ifdef CONFIG_DEVFS_FS
devfs_unregister(devfs_myDriver_raw);
devfs_unregister(devfs_myDriver_dir);
#endif
unregister_chrdev(myGPIO_Major, DRIVER_NAME);
}
return;
}
MODULE_AUTHOR("LiuFan");
MODULE_LICENSE("Dual BSD/GPL");
module_init(myGPIO_init);
module_exit(myGPIO_exit);
設備執行函數功能的實現將在下面完成。如結構體的函數,但并不是全都需要實現。open()函數中是執行一些設備工作前的初始化工作。rlease()則是將設備的相關寄存器恢復到原來的值。read()函數是將設備中的數據拷貝到內核,write()函數是將內核數據拷貝到對應的設備中。MOD_INC_USE_COUNT和MOD_DEC_USE_COUNT兩個宏是提供給系統對硬件資源進行控制訪問的。在open()和rlease()兩個函數中最基本的操作應是實現以上兩個宏的操作。
static unsigned char myGPIO_Buffer[1024*1024];
/* Driver Operation Functions */
static int myGPIO_open(struct inode *inode, struct file *filp)
{
// int Minor = MINOR(inode->i_rdev);
// filp->private_data = 0;
MOD_INC_USE_COUNT;
PRINTK("myDriver open called!\n");
return 0;
}
static int myGPIO_release(struct inode *inode, struct file *filp)
{
// int Minor = MINOR(inode->i_rdev);
MOD_DEC_USE_COUNT;
PRINTK("myDriver release called!\n");
return 0;
}
static ssize_t myGPIO_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
char dat;
size_t read_size = count;
PRINTK("GPIO read called!\n");
PRINTK("\tcount=%d, pos=%d\n", count, (int)*f_pos);
/* if(*f_pos >= sizeof(myGPIO_Buffer))
{
PRINTK("[GPIO read]Buffer Overlap\n");
*f_pos = sizeof(myGPIO_Buffer);
return 0;
}
if((count + *f_pos) > sizeof(myGPIO_Buffer))
{
PRINTK("count + f_pos > sizeof buffer\n");
read_size = sizeof(myGPIO_Buffer) - *f_pos;
}*/
dat= GPFDAT;
copy_to_user(buf,&dat,1);
// *f_pos += read_size;
return read_size;
}
static ssize_t myGPIO_write(struct file *filp,const char *buf, size_t count, loff_t *f_pos)
{
char dat;
size_t fill_size = count;
PRINTK("myDriver write called!\n");
PRINTK("\tcount=%d, pos=%d\n", count, (int)*f_pos);
if(*f_pos >= sizeof(myGPIO_Buffer))
{
PRINTK("[myDriver write]Buffer Overlap\n");
*f_pos = sizeof(myGPIO_Buffer);
return 0;
}
if((count + *f_pos) > sizeof(myGPIO_Buffer))
{
PRINTK("count + f_pos > sizeof buffer\n");
fill_size = sizeof(myGPIO_Buffer) - *f_pos;
}
copy_from_user(&dat,buf,fill_size);
GPFDAT = dat;
// *f_pos += fill_size;
return fill_size;
}
控制ioctl() 函數則是提供給應用層的接口函數,功能并不是固定的,由開發者定義,一般都是對硬件的一些除過上述功能的其他操作。
static int myGPIO_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
int i;
unsigned int mask=0x01;
GPFUP = 0x00;
PRINTK("myGPIO ioctl called(%d)!\n", cmd);
switch(cmd)
{
case MOD_IN:
for(i=0;i<8;i++)
{
if((mask & arg)!=0x0)
{
GPFCON &=~(3<<i*2);
}
mask =mask << 1;
}
break;
case MOD_OUT:
PRINTK("IOCTRL 0 called(0x%lx)!\n", arg);
for(i=0;i<8;i++)
{
if((mask & arg)!=0x00)
{
GPFCON &= ~(3 <<( i*2));
GPFCON |=(0x01<<(i*2));
}
mask=mask<<1;
}
break;
case MOD_EXIT_INT:
PRINTK("IOCTRL 1 called(0x%lx)!\n", arg);
GPFDAT = 0xFFFFFF00;
break;
default:
break;
}
return 0;
}