Step1:驅(qū)動加載函數(shù): ***_init;
該函數(shù)內(nèi)容為驅(qū)動硬件時僅執(zhí)行一次的函數(shù),其作用是硬件初始化。如配置IO端口輸出方向,配置IO上拉等;該函數(shù)傳入在module_init(***_init);函數(shù)中以使得在加載驅(qū)動時自動執(zhí)行;
Step2:寫open,read,write,release函數(shù);
這三個函數(shù)的作用是為了傳入file_operations結(jié)構(gòu)體;在應(yīng)用程序調(diào)用驅(qū)動時這三個函數(shù)作為借口被調(diào)用。 其中open的作用是在編寫應(yīng)用程序時加載驅(qū)動程序;其原型為pen(strpath, authority )如:fd = open("/dev/led",O_RDWR);//open函數(shù)加載驅(qū)動,返回值為描述符,返回值為0時則成功加載驅(qū)動。 Write函數(shù)的原型為: (structfile *filp, const char __user *buf, size_t count,loff_t *f_pos)
Step3:將step2中的函數(shù)賦給operations結(jié)構(gòu)體
struct file_operations led_fops = { .owner= THIS_MODULE, .open= led_open, .read= led_read, .write= led_write, .release= led_release, };
Step4:在***_init函數(shù)中注冊字符型設(shè)備驅(qū)動模塊;如以下為注冊led驅(qū)動函數(shù):
register_chrdev(LED_MAJOR,"led",&led_fops); int register_chrdev(unsignedintmajor,constchar*name,structfile_operations*fops); 其中參數(shù)major如果等于0,則表示采用系統(tǒng)動態(tài)分配的主設(shè)備號;不為0,則表示靜態(tài)注冊。 注銷字符設(shè)備可以使用unregister_chrdev函數(shù)。
Step5:注銷字符型設(shè)備驅(qū)動模塊:
在led_exit(可以任意名字,最后把函數(shù)名傳給module_exit即可)使用unregister_chrdev(LED_MAJOR,"led"); 函數(shù)注銷驅(qū)動。并賦給module_exit(led_exit);
·一個活生生的例子
1.查看用戶手冊 led1、led2、led3、led4 連接的分別是 GPK4、GPK5、GPK6、GPK7 2、查詢6410芯片手冊 
下面還需要3 個步驟: 1、設(shè)置GPIO為OUTPUT。 將GPK4、GPK5、GPK6、GPK7設(shè)置為輸出output=0001 即GPKCON0的19:28都配置為0001
2、設(shè)置GPIO的數(shù)據(jù)。 將GPKDATA的4:7位賦值為0
3、設(shè)置GPKUP為上拉。 將GPKUP的4:7位設(shè)置為10
3、代碼 led_driver.cStep1:驅(qū)動加載函數(shù): ***_init;
該函數(shù)內(nèi)容為驅(qū)動硬件時僅執(zhí)行一次的函數(shù),其作用是硬件初始化。如配置IO端口輸出方向,配置IO上拉等;該函數(shù)傳入在module_init(***_init);函數(shù)中以使得在加載驅(qū)動時自動執(zhí)行;
Step2:寫open,read,write,release函數(shù);
這三個函數(shù)的作用是為了傳入file_operations結(jié)構(gòu)體;在應(yīng)用程序調(diào)用驅(qū)動時這三個函數(shù)作為借口被調(diào)用。 其中open的作用是在編寫應(yīng)用程序時加載驅(qū)動程序;其原型為pen(strpath, authority )如:fd = open("/dev/led",O_RDWR);//open函數(shù)加載驅(qū)動,返回值為描述符,返回值為0時則成功加載驅(qū)動。 Write函數(shù)的原型為: (structfile *filp, const char __user *buf, size_t count,loff_t *f_pos)
Step3:將step2中的函數(shù)賦給operations結(jié)構(gòu)體
struct file_operations led_fops = { .owner= THIS_MODULE, .open= led_open, .read= led_read, .write= led_write, .release= led_release, };
Step4:在***_init函數(shù)中注冊字符型設(shè)備驅(qū)動模塊;如以下為注冊led驅(qū)動函數(shù):
register_chrdev(LED_MAJOR,"led",&led_fops); int register_chrdev(unsignedintmajor,constchar*name,structfile_operations*fops); 其中參數(shù)major如果等于0,則表示采用系統(tǒng)動態(tài)分配的主設(shè)備號;不為0,則表示靜態(tài)注冊。 注銷字符設(shè)備可以使用unregister_chrdev函數(shù)。
Step5:注銷字符型設(shè)備驅(qū)動模塊:
在led_exit(可以任意名字,最后把函數(shù)名傳給module_exit即可)使用unregister_chrdev(LED_MAJOR,"led"); 函數(shù)注銷驅(qū)動。并賦給module_exit(led_exit);
·一個活生生的例子
1.查看用戶手冊 led1、led2、led3、led4 連接的分別是 GPK4、GPK5、GPK6、GPK7 2、查詢6410芯片手冊 
下面還需要3 個步驟: 1、設(shè)置GPIO為OUTPUT。 將GPK4、GPK5、GPK6、GPK7設(shè)置為輸出output=0001 即GPKCON0的19:28都配置為0001
2、設(shè)置GPIO的數(shù)據(jù)。 將GPKDATA的4:7位賦值為0
3、設(shè)置GPKUP為上拉。 將GPKUP的4:7位設(shè)置為10
3、代碼 led_driver.c
- #include <linux/module.h> /*它定義了模塊的 API、類型和宏(MODULE_LICENSE、MODULE_AUTHOR等等),所有的內(nèi)核模塊都必須包含這個頭文件。*/
-
- #include <linux/kernel.h> /*使用內(nèi)核信息優(yōu)先級時要包含這個文件,一般在使用printk函數(shù)時使用到優(yōu)先級信息*/
-
- #include <linux/fs.h>
- #include <asm/uaccess.h> /* copy_to_user,copy_from_user */
- #include <linux/pci.h> /*readl writel*/
- #include <mach/map.h>
- #include <mach/regs-gpio.h>
- #include <mach/gpio-bank-k.h>
-
-
- #define LED_MAJOR 243
-
- #define LED_ON 1
- #define LED_OFF 0
- #define LED_1_ON 2
- #define LED_1_OFF 3
- #define LED_2_ON 4
- #define LED_2_OFF 5
- #define LED_3_ON 6
- #define LED_3_OFF 7
- #define LED_4_ON 8
- #define LED_4_OFF 9
-
-
- static int led_open (struct inode *inode,struct file *filp)
-
- {
- unsigned tmp;
-
- tmp = readl(S3C64XX_GPKCON);
- tmp = (tmp&0x0000ffff)| 0x1111ffff;
- writel(tmp, S3C64XX_GPKCON);
-
- printk("#########open######\n");
- return 0;
- }
-
- static int led_read (struct file *filp, char __user *buf, size_t count,loff_t *f_pos)
- {
- return count;
- }
-
-
- static int led_write (struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)
-
- {
- char wbuf[10];
- unsigned tmp;
-
- if(copy_from_user(wbuf,buf,count))
- return -EFAULT;
-
- switch(wbuf[0])
- {
-
- case LED_ON:
- tmp = readl(S3C64XX_GPKDAT);
- tmp &= (0x0f);
- writel(tmp, S3C64XX_GPKDAT);
- printk("turn on!\n");
- break;
-
- case LED_OFF:
- tmp = readl(S3C64XX_GPKDAT);
- tmp |= (0xf0);
- writel(tmp, S3C64XX_GPKDAT);
- printk("turn off!\n");
- break;
-
- case LED_1_ON:
- tmp = readl(S3C64XX_GPKDAT);
- tmp &= (0xef);
- writel(tmp, S3C64XX_GPKDAT);
- printk("turn off!\n");
- break;
-
- case LED_1_OFF:
- tmp = readl(S3C64XX_GPKDAT);
- tmp |= (0xf0);
- writel(tmp, S3C64XX_GPKDAT);
- printk("turn on!\n");
- break;
-
- case LED_2_ON:
- tmp = readl(S3C64XX_GPKDAT);
- tmp &= (0xdf);
- writel(tmp, S3C64XX_GPKDAT);
- printk("turn off!\n");
- break;
-
- case LED_2_OFF:
- tmp = readl(S3C64XX_GPKDAT);
- tmp |= (0xf0);
- writel(tmp, S3C64XX_GPKDAT);
- printk("turn on!\n");
- break;
-
- case LED_3_ON:
- tmp = readl(S3C64XX_GPKDAT);
- tmp &= (0xbf);
- writel(tmp, S3C64XX_GPKDAT);
- printk("turn off!\n");
- break;
-
- case LED_3_OFF:
- tmp = readl(S3C64XX_GPKDAT);
- tmp |= (0xf0);
- writel(tmp, S3C64XX_GPKDAT);
- printk("turn on!\n");
- break;
-
- case LED_4_ON:
- tmp = readl(S3C64XX_GPKDAT);
- tmp &= (0x7f);
- writel(tmp, S3C64XX_GPKDAT);
- printk("turn off!\n");
- break;
-
- case LED_4_OFF:
- tmp = readl(S3C64XX_GPKDAT);
- tmp |= (0xf0);
- writel(tmp, S3C64XX_GPKDAT);
- printk("turn on!\n");
- break;
-
-
- default :
- break;
- }
- return 0;
- }
-
- int led_release (struct inode *inode, struct file *filp)
- {
- printk("#########release######\n");
- return 0;
- }
-
- struct file_operations led_fops =
- {
- .owner = THIS_MODULE,
- .open = led_open,
- .read = led_read,
- .write = led_write,
- .release = led_release,
- };
-
- int __init led_init (void)
- {
- int rc;
- printk ("Test led dev\n");
- rc = register_chrdev(LED_MAJOR,"led",&led_fops);
-
- if (rc <0)
- {
- printk ("register %s char dev error\n","led");
- return -1;
- }
- printk ("ok!\n");
- return 0;
- }
-
- void __exit led_exit (void)
- {
- unregister_chrdev(LED_MAJOR,"led");
- printk ("module exit\n");
- return ;
- }
-
- module_init(led_init);
- module_exit(led_exit);
- Makefile
- [cpp] view plain copy
- print?
- obj-m := led_driver.o
- KDIR :=/home/workdir/kernel/linux-2.6.38
- all:
- make -C $(KDIR) M=$(shell pwd) modules
- install:
- cp driver_led.ko /tftpboot/
- clean:
- make -C $(KDIR) M=$(shell pwd) clean
- 測試文件
- test_led.c
- [cpp] view plain copy
- print?
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
-
-
- #define LED_OFF 0
- #define LED_ON 1
- #define LED_1_ON 2
- #define LED_1_OFF 3
- #define LED_2_ON 4
- #define LED_2_OFF 5
- #define LED_3_ON 6
- #define LED_3_OFF 7
- #define LED_4_ON 8
- #define LED_4_OFF 9
-
- int main (void)
- {
- int i=0;
- int fd;
- char buf[10]={
- LED_ON , LED_OFF ,
- LED_1_ON, LED_1_OFF,
- LED_2_ON, LED_2_OFF,
- LED_3_ON, LED_3_OFF,
- LED_4_ON, LED_4_OFF,
- };
-
- fd = open("/dev/led",O_RDWR);
- if (fd < 0)
- {
- printf ("Open /dev/led file error\n");
- return -1;
- }
-
- while(i<10)
- {
- write(fd,&buf[i],4);
- sleep(1);
- i++;
- }
- close (fd);
- return 0;
-
- }
復制代碼
上述編譯沒有問題,就可以下到板子測試了。
加載驅(qū)動 insmod led_driver.ko
創(chuàng)建設(shè)備文件 mknod /dev/led c 243 0 其中243要跟驅(qū)動文件中的設(shè)備號一致
運行測試文件 ./test_led
完成。
------------------------------------------------------------------------------------------
參考資料:
- #include <linux/module.h>/*它定義了模塊的 API、類型和宏(MODULE_LICENSE、MODULE_AUTHOR等等),所有的內(nèi)核模塊都必須包含這個頭文件。/
- #include <linux/kernel.h>/*使用內(nèi)核信息優(yōu)先級時要包含這個文件,一般在使用printk函數(shù)時使用到優(yōu)先級信息*/
- #include <linux/init.h>//頭文件:module_init、module_exit等宏定義。
- #include <linux/fs.h>////struct file_operations
- #include <asm/irq.h>
- #include <mach/regs-gpio.h>// S3C2410 GPIO寄存器定義
- #include <mach/hardware.h>// s3c2410_gpio_setpin, s3c2410_gpio_cfgpin等
- #include <linux/device.h>//class_create device_create(注意,有些2.6.27以前是的可能是class_device_create,如果出現(xiàn)implicate 錯誤時,看一下這個頭問題里邊是哪一個),udev,自動在/dev下創(chuàng)建設(shè)備節(jié)點
- #include <linux/cdev.h>//字符設(shè)備節(jié)點注冊,函數(shù)有cdev_init,cdev_add,cdev_del等早期的辦法是register_chrdev,unregister_chrdev這種方法應(yīng)避免使用。
- #define DEVICE_NAME "leds" /* 加載模式后,執(zhí)行”cat /proc/devices”命令看到的設(shè)備名稱 */
- #define LED_MAJOR 231 /* 主設(shè)備號 */
- /* 應(yīng)用程序執(zhí)行ioctl(fd, cmd, arg)時的第2個參數(shù) */
- #define IOCTL_LED_ON 1
- #define IOCTL_LED_OFF 0
- /* 用來指定LED所用的GPIO引腳 */
- static unsigned long led_table [] =
- {
- S3C2410_GPB5,
- S3C2410_GPB6,
- S3C2410_GPB7,
- S3C2410_GPB8,
- };
- /* 用來指定GPIO引腳的功能:輸出 */
- static unsigned int led_cfg_table [] =
- {
- S3C2410_GPB5_OUTP,
- S3C2410_GPB6_OUTP,
- S3C2410_GPB7_OUTP,
- S3C2410_GPB8_OUTP,
- };
- struct leds_type
- {
- struct cdev cdev;
- };
- struct leds_type *my_leds_dev;
- /* 應(yīng)用程序?qū)υO(shè)備文件/dev/EmbedSky-leds執(zhí)行open(...)時,
- * 就會調(diào)用EmbedSky_leds_open函數(shù)
- */
- static int EmbedSky_leds_open(struct inode *inode, struct file *file)
- {
- int i;
- for (i = 0; i < 4; i++)
- {
- // 設(shè)置GPIO引腳的功能:本驅(qū)動中LED所涉及的GPIO引腳設(shè)為輸出功能
- s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);
- }
- return 0;
- }
- /* 應(yīng)用程序?qū)υO(shè)備文件/dev/EmbedSky-leds執(zhí)行ioclt(...)時,
- * 就會調(diào)用EmbedSky_leds_ioctl函數(shù)
- */
- static int EmbedSky_leds_ioctl( struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
- {
- if (arg > 4)
- {
- return -EINVAL;
- }
- switch(cmd)
- {
- case IOCTL_LED_ON:
- // 設(shè)置指定引腳的輸出電平為0
- s3c2410_gpio_setpin(led_table[arg], 0);
- return 0;
- case IOCTL_LED_OFF:
- // 設(shè)置指定引腳的輸出電平為1
- s3c2410_gpio_setpin(led_table[arg], 1);
- return 0;
- default:
- return -EINVAL;
- }
- }
- /* 這個結(jié)構(gòu)是字符設(shè)備驅(qū)動程序的核心
- * 當應(yīng)用程序操作設(shè)備文件時所調(diào)用的open、read、write等函數(shù),
- * 最終會調(diào)用這個結(jié)構(gòu)中指定的對應(yīng)函數(shù)
- */
- static struct file_operations EmbedSky_leds_fops =
- {
- .owner = THIS_MODULE, /* 這是一個宏,推向編譯模塊時自動創(chuàng)建的__this_module變量 */
- .open = EmbedSky_leds_open,
- .ioctl = EmbedSky_leds_ioctl,
- };
- static char __initdata banner[] = "TQ2440/SKY2440 LEDS, (c) 2008,2009 www.embedsky.net/n";
- static struct class *led_class;
- /*
- * 執(zhí)行“insmod EmbedSky_leds.ko”命令時就會調(diào)用這個函數(shù)
- */
- static int __init EmbedSky_leds_init(void)
- {
- int ret;
- dev_t devno=MKDEV(LED_MAJOR,0);
- printk("init led/n");
- printk(banner);
- /* 注冊字符設(shè)備驅(qū)動程序
- * 參數(shù)為主設(shè)備號、設(shè)備名字、file_operations結(jié)構(gòu);
- * 這樣,主設(shè)備號就和具體的file_operations結(jié)構(gòu)聯(lián)系起來了,
- * 操作主設(shè)備為LED_MAJOR的設(shè)備文件時,就會調(diào)用EmbedSky_leds_fops中的相關(guān)成員函數(shù)
- ret = register_chrdev_region(devno, 1,DEVICE_NAME);//獲得設(shè)備編號
- my_leds_dev=kmalloc(sizeof(struct leds_type),GFP_KERNEL);
- /*這個必須有不然會在加載模塊時出現(xiàn)Unable to handle kernel NULL pointer dereference at virtual addres 00000000 錯誤,這是由于在這里my_leds_dev僅僅是個指針,沒有相應(yīng)大小的分配內(nèi)存,所以使用時會出錯,,,尋找這個錯誤是比較麻煩的*/
- if(!my_leds_dev)
- {
- ret=-ENOMEM;
- goto fail_malloc;
- }
- memset(my_leds_dev,0,sizeof(struct leds_type));
- cdev_init(&(my_leds_dev->cdev),&EmbedSky_leds_fops);
- ret=cdev_add(&(my_leds_dev->cdev),devno,1);
- /*注意:與早期的設(shè)備注冊方法不同,早期的直接register_chrdev()就可以,*/
- if(ret)printk(KERN_NOTICE"ERROR %d",ret);
- //注冊一個類,使mdev可以在"/dev/"目錄下面建立設(shè)備節(jié)點
- led_class = class_create(THIS_MODULE, DEVICE_NAME);
- if(IS_ERR(led_class))
- {
- printk("Err: failed in EmbedSky-leds class. /n");
- return -1;
- }
- //創(chuàng)建一個設(shè)備節(jié)點,節(jié)點名為DEVICE_NAME
- device_create(led_class, NULL, MKDEV(LED_MAJOR, 0), NULL, DEVICE_NAME);
- printk(DEVICE_NAME " initialized/n");
- return 0;
- fail_malloc: unregister_chrdev_region(devno,1);
- return ret;
- }
- /*
- * 執(zhí)行”rmmod EmbedSky_leds.ko”命令時就會調(diào)用這個函數(shù)
- */
- static void __exit EmbedSky_leds_exit(void)
- {
- /* 卸載驅(qū)動程序 */
- unregister_chrdev(LED_MAJOR, DEVICE_NAME);
- device_destroy(led_class, MKDEV(LED_MAJOR, 0)); //刪掉設(shè)備節(jié)點
- class_destroy(led_class); //注銷類
- }
- /* 這兩行指定驅(qū)動程序的初始化函數(shù)和卸載函數(shù) */
- module_init(EmbedSky_leds_init);
- module_exit(EmbedSky_leds_exit);
- /* 描述驅(qū)動程序的一些信息,不是必須的 */
- MODULE_AUTHOR("http://www.embedsky.net"); // 驅(qū)動程序的作者
- MODULE_DESCRIPTION("TQ2440/SKY2440 LED Driver"); // 一些描述信息
- MODULE_LICENSE("GPL"); // 遵循的協(xié)議
- 上面代碼中,led_table數(shù)組相當于對應(yīng)了GPB的四個IO口的索引,通過這四個值,對這四個IO口進行相關(guān)操作。例如:
- S3C2410_GPB5 = S3C2410_GPIONO(S3C2410_GPIO_BANKB, 5)
- = S3C2410_GPIO_BANKB + 5
- = 32*1 + 5
- 在s3c2410_gpio_setpin(S3C2410_GPB5,0)中,該函數(shù)首先通過S3C2410_GPB5獲得GPB的虛擬地址和偏移地址,再對GPB5的GPBDAT寄存器進行操作,具體
- void s3c2410_gpio_setpin(unsigned int pin, unsigned int to)
- {
- void __iomem *base = S3C2410_GPIO_BASE(pin);
- unsigned long offs = S3C2410_GPIO_OFFSET(pin);
- unsigned long flags;
- unsigned long dat;
- local_irq_save(flags);
- dat = __raw_readl(base + 0x04);//讀取GPIO的DAT數(shù)據(jù)到dat
- dat &= ~(1 << offs); //先將要設(shè)置的IO口拉低
- dat |= to << offs; //再將形參的to值賦給dat
- __raw_writel(dat, base + 0x04);//最后將DAT值寫進GPIO的DAT
- local_irq_restore(flags);
- }
- 上面的 函數(shù)調(diào)用了兩個子函數(shù),具體定義如下
- #define S3C2410_GPIO_BASE(pin) ((((pin) & ~31) >> 1) + S3C24XX_VA_GPIO)
- #define S3C2410_GPIO_OFFSET(pin) ((pin) & 31)
- 其中S3C24XX_VA_GPIO定義如下:
- #define S3C24XX_VA_GPIO S3C2410_ADDR(0x00E00000)
- #define S3C2410_ADDR(x) (0xF0000000 + (x))
- 這里S3C2410_ADDR的基地址為0xF0000000(??),也即2440所有寄存器的虛擬地址的基地址。0x00E00000表示2440的GPIO的偏移地址,也就是說其GPIO的虛擬地址首地址為0xF0E00000。
- 再看看S3C2410_GPIO_BASE(pin)的定義,我們不仿把S3C2410_GPB5的值放進去計算,可以得到(S3C2410_GPB5&~31)=32。其目的就是去掉GPB的偏移值,然后再右移一位,和GPIO的虛擬地址首地址相加。因此,S3C2410_GPIO_BASE(pin)只代表了對應(yīng)GPIO組的虛擬地址,如GPB的虛擬地址為10000(B)+0xF0E00000=0xF0E00010。依此類推,可以得到所有GPIO的偏移地址,具體如下表:
- BANK
- (pin&~31)
- (pin&~31)>>1
- S3C2410_GPIO_BASE(pin)
- GPA
- 32*0
- 0000,0000
- 0x00
- 0xF0E00000
- GPB
- 32*1
- 0010,0000
- 0x10
- 0xF0E00010
- GPC
- 32*2
- 0100,0000
- 0x20
- 0xF0E00020
- GPD
- 32*3
- 0110,0000
- 0x30
- 0xF0E00030
- GPE
- 32*4
- 1000,0000
- 0x40
- 0xF0E00040
- GPF
- 32*5
- 1010,0000
- 0x50
- 0xF0E00050
- GPG
- 32*6
- 1100,0000
- 0x60
- 0xF0E00060
- GPH
- 32*7
- 1110,0000
- 0x70
- 0xF0E00070
- S3C2410_GPIO_OFFSET用于獲得具體GPIO的偏移地址。如GPB5,則S3C2410_GPIO_OFFSET(pin) = (pin)&31 = (32*1 + 5) & 31 = 5。有了*base和off,就可以操作具體的寄存器了。
- 函數(shù)s3c2410_gpio_cfgpin()用于配置GPCON寄存器。具體代碼
- void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function)
- {
- void __iomem *base = S3C2410_GPIO_BASE(pin);
- unsigned long mask;
- unsigned long con;
- unsigned long flags;
- if (pin < S3C2410_GPIO_BANKB)
- {
- mask = 1 << S3C2410_GPIO_OFFSET(pin);//GPA的寄存器只占一位
- }
- else
- {
- mask = 3 << S3C2410_GPIO_OFFSET(pin)*2;//非GPA的寄存器占兩位
- }
- local_irq_save(flags);
- con = __raw_readl(base + 0x00);//先保留GPCON的值
- con &= ~mask; //再將要設(shè)置的管腳的CON值清零
- con |= function; //然后將形參傳進來的配置賦給CON
- __raw_writel(con, base + 0x00); //最后將CON值寫進GPCON寄存器
- local_irq_restore(flags);
- }
- 上面的LED驅(qū)動程序中,led_cfg_table數(shù)組給出了GPB相應(yīng)管腳的屬性設(shè)置,調(diào)用上面的函數(shù)后即設(shè)置為Output。
- 到此為止,整個S3C2440的IO口操作,應(yīng)該就一目了然了
- #define __raw_writel(v,a) (__chk_io_ptr(a), *(volatile unsigned int __force *)(a) = (v))
- # define __chk_io_ptr(x) (void)0
- __chk_io_ptr()是編譯器為了更細致地檢查參數(shù)的屬性,用于調(diào)試,正常編譯時沒有作用。
- volatile為了防止Compiler優(yōu)化。
- 內(nèi)核中,對所有的地址都是通過虛擬地址進行訪問的.因此,不能直接訪問0x56000010的物理地址,如果要對0x56000010的物理地址進行訪問(一般是外設(shè)寄存器),那么需要把0x56000010的物理地址映射為虛擬地址,然后對該虛擬地址進行訪問就是對實際的物理地址進行訪問了。
- 需要注意的是:在進行映射時,該虛擬地址需要disable cache和Write Buffer, 否則就是加了volatile也是沒有用的
- 這個IOREMAP的實現(xiàn)過程中
- /*
- * figure out the physical address offset in a page size
- * PAGE_MASK = (1 << 10)
- */
- offset = phys_addr &~ PAGE_MASK;
- /*
- * figure out physical address with page align
- */
- phys_addrs &= PAGE_MASK;
- /*
- * get the real size with page align
- */
- size = PAGE_ALIGN(last_addr) - phys_addrs;
- 下面是通過vmlist中查找以size大小的空閑塊,所以從這里可以看出,已經(jīng)做過了頁的對齊,只以映射的大小
- 雜項設(shè)備(misc device)
- 雜項設(shè)備也是在嵌入式系統(tǒng)中用得比較多的一種設(shè)備驅(qū)動。在 Linux 內(nèi)核的include/linux目錄下有Miscdevice.h文件,要把自己定義的misc device從設(shè)備定義在這里。其實是因為這些字符設(shè)備不符合預先確定的字符設(shè)備范疇,所有這些設(shè)備采用主編號10 ,一起歸于misc device,其實misc_register就是用主標號10調(diào)用register_chrdev()的。
- 也就是說,misc設(shè)備其實也就是特殊的字符設(shè)備。
- 字符設(shè)備(char device)
- 使用register_chrdev(LED_MAJOR,DEVICE_NAME,&dev_fops)注冊字符設(shè)備驅(qū)動程序時,如果有多個設(shè)備使用該函數(shù)注冊驅(qū)動程序,LED_MAJOR不能相同,否則幾個設(shè)備都無法注冊(我已驗證)。如果模塊使用該方式注冊并且 LED_MAJOR為0(自動分配主設(shè)備號 ),使用insmod命令加載模塊時會在終端顯示分配的主設(shè)備號和次設(shè)備號,在/dev目錄下建立該節(jié)點,比如設(shè)備leds,如果加載該模塊時分配的主設(shè)備號和次設(shè)備號為253和0,則建立節(jié)點:mknod leds c 253 0。使用register_chrdev (LED_MAJOR,DEVICE_NAME,&dev_fops)注冊字符設(shè)備驅(qū)動程序時都要手動建立節(jié)點 ,否則在應(yīng)用程序無法打開該設(shè)備。
- __raw_readl和__raw_writel
- Linux對I/O的操作都定義在asm/io.h中,相應(yīng)的在arm平臺下,就在asm-arm/io.h中。
- #define __raw_writeb(v,a) (__chk_io_ptr(a), *(volatile unsigned char __force *)(a) = (v))
- #define __raw_writew(v,a) (__chk_io_ptr(a), *(volatile unsigned short __force *)(a) = (v))
- #define __raw_writel(v,a) (__chk_io_ptr(a), *(volatile unsigned int __force *)(a) = (v))在include/linux/compiler.h中:
- #ifdef __CHECKER__……
- extern void __chk_io_ptr(void __iomem *);
- #else……
- # define __chk_io_ptr(x) (void)0……
- #endif
- __raw_readl(a)展開是:((void)0, *(volatile unsigned int _force *)(a))。在定義了__CHECKER__的時候先調(diào)用__chk_io_ptr檢查該地址,否則__chk_io_ptr什么也不做,*(volatile unsigned int _force *)(a)就是返回地址為a處的值。(void)xx的做法有時候是有用的,例如編譯器打開了檢查未使用的參數(shù)的時候需要將沒有用到的參數(shù)這么弄一下才能編譯通過。
- CPU對I/O的物理地址的編程方式有兩種:一種是I/O映射,一種是內(nèi)存映射。__raw_readl和__raw_writel等是原始的操作I/O的方法,由此派生出來的操作方法有:inb、outb、_memcpy_fromio、readb、writeb、ioread8、iowrite8等。
復制代碼
|