混雜字符設(shè)備的主要特點(diǎn)是主設(shè)備號(hào)(10)公用,通過(guò)一個(gè)鏈表將各個(gè)設(shè)備關(guān)聯(lián)起來(lái),設(shè)備的識(shí)別主要依靠次設(shè)備號(hào)。
混雜設(shè)備存在自己的結(jié)構(gòu)體:
- struct device;
- struct miscdevice {
- int minor;
- const char *name;
- const struct file_operations *fops;
- struct list_head list;
- struct device *parent;
- struct device *this_device;
- };
其中struct device*可以聯(lián)想到自動(dòng)加載設(shè)備文件中的class_create()和device_create()兩個(gè)函數(shù)。因此樂(lè)意推測(cè)混雜字符設(shè)備是自動(dòng)加載設(shè)備文件的設(shè)備驅(qū)動(dòng)。
其中主要的兩個(gè)函數(shù)分別是misc_register()和misc_deregister(),分別用來(lái)添加和去除混雜設(shè)備。這兩個(gè)函數(shù)分別在初始化函數(shù)和卸載函數(shù)中調(diào)用。
- static struct class *misc_class;
- static const struct file_operations misc_fops = {
- .owner = THIS_MODULE,
- .open = misc_open,
- };
-
- int misc_register(struct miscdevice * misc)
- {
- struct miscdevice *c;
- dev_t dev;
- int err = 0;
- INIT_LIST_HEAD(&misc->list);
- mutex_lock(&misc_mtx);
- list_for_each_entry(c, &misc_list, list) {
- if (c->minor == misc->minor) {
- mutex_unlock(&misc_mtx);
- return -EBUSY;
- }
- }
- if (misc->minor == MISC_DYNAMIC_MINOR) {
- int i = DYNAMIC_MINORS;
- while (--i >= 0)
- if ( (misc_minors[i>>3] & (1 << (i&7))) == 0)
- break;
- if (i<0) {
- mutex_unlock(&misc_mtx);
- return -EBUSY;
- }
- misc->minor = i;
- }
- if (misc->minor < DYNAMIC_MINORS)
- misc_minors[misc->minor >> 3] |= 1 << (misc->minor & 7);
- dev = MKDEV(MISC_MAJOR, misc->minor);
- /*創(chuàng)建設(shè)備*/
- misc->this_device = device_create(misc_class, misc->parent, dev, NULL,
- "%s", misc->name);
- if (IS_ERR(misc->this_device)) {
- err = PTR_ERR(misc->this_device);
- goto out;
- }
- list_add(&misc->list, &misc_list);
- out:
- mutex_unlock(&misc_mtx);
- return err;
- }
int misc_deregister(struct miscdevice *misc)
{
int i = misc->minor;
if (list_empty(&misc->list))
return -EINVAL;
mutex_lock(&misc_mtx);
list_del(&misc->list);
/*釋放設(shè)備*/
device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));
if (i < DYNAMIC_MINORS && i>0) {
misc_minors[i>>3] &= ~(1 << (misc->minor & 7));
}
mutex_unlock(&misc_mtx);
return 0;
}
static int __init misc_init(void)
{
int err;
...
/*創(chuàng)建一個(gè)設(shè)備類*/
misc_class = class_create(THIS_MODULE, "misc");
...
return err;
}
從源碼中可以知道混雜字符設(shè)備就是自動(dòng)創(chuàng)建設(shè)備文件的設(shè)備驅(qū)動(dòng)。
LED 的字符設(shè)備驅(qū)動(dòng),由于沒(méi)有讀寫操作,只需要完成最控制操作,也就是ioctl函數(shù)的實(shí)現(xiàn)。由于open函數(shù)默認(rèn)情況下就是打開(kāi),所以不去實(shí)現(xiàn)也是可以的。 ioctl函數(shù)的實(shí)現(xiàn)主要包含兩個(gè)步驟,分別是定義命令和實(shí)現(xiàn)命令。其中的定義命令包含類型、方向、數(shù)據(jù)大小、以及命令序號(hào),這些都可以按著一定宏定義實(shí)現(xiàn)。實(shí)現(xiàn)控制也就是ioctl函數(shù)的定義,其中包含,三部分:(1)、命令的檢查,類型和序號(hào);(2)、指針參數(shù)的可讀可寫檢查;(3)具體命令的實(shí)現(xiàn) (switch-case)。
LED的實(shí)現(xiàn)主要就是控制全亮、全滅,某一個(gè)亮,某一個(gè)滅。我的開(kāi)發(fā)板是TQ2440,利用了GPB5-GPB8來(lái)控制4個(gè)LED,只要當(dāng)端口為低電平時(shí),LED亮,高電平時(shí),LED滅。
具體的實(shí)現(xiàn)如下:
- #include<linux/module.h>
- #include<linux/types.h>
- #include<linux/fs.h>
- #include<linux/sched.h>
- #include<linux/init.h>
- #include<linux/cdev.h>
- #include<linux/device.h>
- #include<linux/mm.h>
- #include<linux/miscdevice.h>
- /*平臺(tái)相關(guān)的頭文件*/
- #include<mach/regs-gpio.h>
- #include<mach/hardware.h>
- #include<linux/errno.h>
- #include<linux/gpio.h>
- #include<linux/cdev.h>
- #include<linux/slab.h>
- #include<linux/string.h>
- #include<linux/kernel.h>
- /*定義自己的命令*/
- /*定義幻數(shù),表示具體的設(shè)備*/
- #define LED_MAGIC_NUMBER 'k'
- #define LED_ALL_ON _IO(LED_MAGIC_NUMBER,0)
- #define LED_ALL_OFF _IO(LED_MAGIC_NUMBER,1)
- #define LED_ON _IO(LED_MAGIC_NUMBER,2)
- #define LED_OFF _IO(LED_MAGIC_NUMBER,3)
- #define LED_MAX_CMD 4
- /*設(shè)備名*/
- #define DEVICE_NAME "GP_LED"
/*具體的端口號(hào)*/
- static unsigned int led_table[] =
- {
- S3C2410_GPB5,
- S3C2410_GPB6,
- S3C2410_GPB7,
- S3C2410_GPB8,
- };
- /*端口的功能數(shù)組*/
- static unsigned int led_cfg_table[]=
- {
- S3C2410_GPB5_OUTP,
- S3C2410_GPB6_OUTP,
- S3C2410_GPB7_OUTP,
- S3C2410_GPB8_OUTP,
- /*或者采用通用功能*/
- /*
- S3C2410_GPIO_OUTPUT,
- S3C2410_GPIO_OUTPUT,
- S3C2410_GPIO_OUTPUT,
- S3C2410_GPIO_OUTPUT,
- */
- };
- static int s3c2440_led_ioctl(
- struct inode * inode,
- struct file *file,
- unsigned int cmd,
- unsigned long arg
- )
- {
- int i = 0;
- /*檢測(cè)參數(shù)的正確性*/
- if(_IOC_TYPE(cmd)!=LED_MAGIC_NUMBER)
- return -EINVAL;
- /*檢查命令是否超過(guò)一定的界限*/
- if(_IOC_NR(cmd) >= LED_MAX_CMD)
- return -EINVAL;
- /*檢查arg參數(shù)的正確性*/
- if(arg<0 || arg >4)
- {
- return -EINVAL;
- }
- /*命令控制語(yǔ)句*/
- switch(cmd)
- {
- case LED_ALL_ON:
- {
- for(i = 0; i < 4; ++ i)
- s3c2410_gpio_setpin(led_table[arg-i-1],0);
- break;
- }
- case LED_ALL_OFF:
- {
- for(i = 0; i < 4; ++ i)
- s3c2410_gpio_setpin(led_table[arg-i-1],1);
- break;
- }
- case LED_ON:
- {
- s3c2410_gpio_setpin(led_table[arg],0);
- break;
- }
- case LED_OFF:
- {
- s3c2410_gpio_setpin(led_table[arg],1);
- break;
- }
- default:
- {
- return -EINVAL;
- break;
- }
- }
- return 0;
- }
- /*具體函數(shù)*/
- static const struct file_operations led_fops =
- {
- .owner = THIS_MODULE,
- .ioctl = s3c2440_led_ioctl,
- };
- /*混雜設(shè)備類*/
- static const struct miscdevice misc =
- {
- .minor = MISC_DYNAMIC_MINOR,
- .name = DEVICE_NAME,
- /*此處是一個(gè)地址,而不是一個(gè)數(shù)*/
- .fops = &led_fops,
- };
- /*初始化*/
- static int __init dev_init(void)
- {
- int ret;
- int i;
- for(i = 0; i<4; ++i)
- {
- s3c2410_gpio_cfgpin(led_table[i],led_cfg_table[i]);
- s3c2410_gpio_setpin(led_table[i],1);
- }
- ret = misc_register(&misc);
- printk(DEVICE_NAME"\tinitialized\n");
- return ret;
- }
- /*退出*/
- static void __exit dev_exit(void)
- {
- misc_deregister(&misc);
- }
- module_init(dev_init);
- module_exit(dev_exit);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("GP");
測(cè)試應(yīng)用程序如下:
- #include<stdio.h>
- #include<stdlib.h>
- #include<unistd.h>
- #include<fcntl.h>
- #include<sys/ioctl.h>
- #define LED_MAGIC_NUMBER 'k'
- #define LED_ALL_ON _IO(LED_MAGIC_NUMBER,0)
- #define LED_ALL_OFF _IO(LED_MAGIC_NUMBER,1)
- #define LED_ON _IO(LED_MAGIC_NUMBER,2)
- #define LED_OFF _IO(LED_MAGIC_NUMBER,3)
- int main(int argc,char *argv[])
- {
- int fd,cmd;
- unsigned int arg;
- if(argc != 3)
- {
- printf("parameter is not right");
- exit(-1);
- }
- cmd = atoi(argv[1]);
- arg = atoi(argv[2]);
- if(cmd > 3 || cmd < 0 || arg > 4 || arg < 0)
- {
- printf("The style of command is not right\n");
- exit(-1);
- }
- fd = open("/dev/GP_LED",O_RDWR);
- if(fd == -1)
- {
- printf("Open File wrong!!\n");
- exit(-1);
- }
- switch(cmd)
- {
- case 0:
- cmd = LED_ALL_ON;
- arg = 4;
- break;
- case 1:
- cmd = LED_ALL_OFF;
- arg = 4;
- break;
- case 2:
- cmd = LED_ON;
- break;
- case 3:
- cmd = LED_OFF;
- break;
- default:
- exit(-1);
- }
- int isOk = ioctl(fd,cmd,arg);
- printf("%d",isOk);
- close(fd);
- exit(0);
- }
分析代碼:
應(yīng)用程序沒(méi)什么好分析的,關(guān)鍵是驅(qū)動(dòng)代碼中的幾個(gè)重要的數(shù)據(jù)結(jié)構(gòu)S3C2410_GPB5-S3C2410_GPB8以及 S3C2410_GPB5_OUTP--S3C2410_GPB8_OUTP和兩個(gè)函數(shù) s3c2410_gpio_cfgpin(),s3c2410_gpio_setpin()。
其中3C2410_GPB5- S3C2410_GPB8是指GPB5-GPB8這四個(gè)IO口,Linux中對(duì)端口都進(jìn)行了編號(hào),給予每一個(gè)IO口唯一的端口號(hào)。同時(shí)又將端口分成了很多塊GPx,包括GPA,GPB,...,GPH等。每一塊的起始端口號(hào)為(x-1)*32+0,也就是GPA的起始端口號(hào)為0,而GPB的起始端口號(hào)為 32,依此類推。而S3C2410_GPB5_OUTP是指將GPB5配置為輸出口,每一個(gè)IO口都是多功能IO,使用前都需要進(jìn)行配置。
具體的源碼如下:
- ...
- /*得到端口號(hào),每一個(gè)IO口的端口號(hào)是唯一的*/
- #define S3C2410_GPB5 S3C2410_GPIONO(S3C2410_GPIO_BANKB, 5)
- /*定義端口的功能,其中的<<10,是因?yàn)樵贕PBCON的第10bit開(kāi)始是配置端口B的功能,其他的也類似,只是位不同*/
- #define S3C2410_GPB5_INP (0x00 << 10)
- #define S3C2410_GPB5_OUTP (0x01 << 10)
- #define S3C2410_GPB5_nXBACK (0x02 << 10)
- #define S3C2443_GPB5_XBACK (0x03 << 10)
- #define S3C2400_GPB5_DATA21 (0x02 << 10)
- #define S3C2400_GPB5_nCTS1 (0x03 << 10)
- #define S3C2410_GPB6 S3C2410_GPIONO(S3C2410_GPIO_BANKB, 6)
- #define S3C2410_GPB6_INP (0x00 << 12)
- #define S3C2410_GPB6_OUTP (0x01 << 12)
- #define S3C2410_GPB6_nXBREQ (0x02 << 12)
- #define S3C2443_GPB6_XBREQ (0x03 << 12)
- #define S3C2400_GPB6_DATA22 (0x02 << 12)
- #define S3C2400_GPB6_nRTS1 (0x03 << 12)
- #define S3C2410_GPB7 S3C2410_GPIONO(S3C2410_GPIO_BANKB, 7)
- #define S3C2410_GPB7_INP (0x00 << 14)
- #define S3C2410_GPB7_OUTP (0x01 << 14)
- #define S3C2410_GPB7_nXDACK1 (0x02 << 14)
- #define S3C2443_GPB7_XDACK1 (0x03 << 14)
- #define S3C2400_GPB7_DATA23 (0x02 << 14)
- #define S3C2410_GPB8 S3C2410_GPIONO(S3C2410_GPIO_BANKB, 8)
- #define S3C2410_GPB8_INP (0x00 << 16)
- #define S3C2410_GPB8_OUTP (0x01 << 16)
- #define S3C2410_GPB8_nXDREQ1 (0x02 << 16)
- #define S3C2400_GPB8_DATA24 (0x02 << 16)
- ...
...
- #define S3C2410_GPIONO(bank,offset) ((bank) + (offset))
- /*將IO分成8塊,便于管理同一類型的端口*/
- #define S3C2410_GPIO_BANKA (32*0)
- #define S3C2410_GPIO_BANKB (32*1)
- #define S3C2410_GPIO_BANKC (32*2)
- #define S3C2410_GPIO_BANKD (32*3)
- #define S3C2410_GPIO_BANKE (32*4)
- #define S3C2410_GPIO_BANKF (32*5)
- #define S3C2410_GPIO_BANKG (32*6)
- #define S3C2410_GPIO_BANKH (32*7)
兩個(gè)函數(shù)s3c2410_gpio_cfgpin(),s3c2410_gpio_setpin()分別表示配置端口(配置功能寄存器)和設(shè)置端口(寫讀數(shù)據(jù)寄存器)。
在linux內(nèi)核中,通常將將CPU和外設(shè)的寄存器從物理地址靜態(tài)的映射到了虛擬地址空間中以固定地址開(kāi)始的一段內(nèi)存空間上。
S3C24XXCPU的CPU和外設(shè)寄存器映射關(guān)系分布如下圖所示:
具體的實(shí)現(xiàn)參看源碼:
- /*配置端口的功能寄存器*/
- void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function)
- {
- void __iomem *base = S3C24XX_GPIO_BASE(pin);
- /*mask可以用來(lái)清零,或者置位*/
- unsigned long mask;
- unsigned long con;
- unsigned long flags;
- /*針對(duì)GPA的偏移量,因?yàn)橹挥袃煞N功能,所以只有1bit表示,偏移量也只需要1*/
- if (pin < S3C2410_GPIO_BANKB) {
- /*用于將當(dāng)前端口所在的位置位或者清零
- 清零:con &= ~mask;
- 置位:con |= mask;
- */
- mask = 1 << S3C2410_GPIO_OFFSET(pin);
- }
- /*針對(duì)GPB開(kāi)始的端口,因?yàn)楣δ鼙容^多,需要兩個(gè)bit描述一個(gè)端口的功能,所以偏移量乘以2*/
- else {
- /*同樣也可以實(shí)現(xiàn)將對(duì)應(yīng)的兩個(gè)位置位或者清零*/
- mask = 3 << S3C2410_GPIO_OFFSET(pin)*2;
- }
- /*根據(jù)功能設(shè)置相應(yīng)的設(shè)置相應(yīng)的位操作,這一部分主要是為了實(shí)現(xiàn)對(duì)常規(guī)命令解析*/
- switch (function) {
- case S3C2410_GPIO_LEAVE:
- mask = 0;
- function = 0;
- break;
- /*采用通用命令的形式,需要對(duì)相應(yīng)的位進(jìn)行解析,因此驅(qū)動(dòng)中也可以將S3C2410_GPB5_OUTP置換為S3C2410_GPIO_OUTPUT*/
- case S3C2410_GPIO_INPUT:
- case S3C2410_GPIO_OUTPUT:
- case S3C2410_GPIO_SFN2:
- case S3C2410_GPIO_SFN3:
- /*從通用方法中提取對(duì)應(yīng)端口的功能*/
- if (pin < S3C2410_GPIO_BANKB) {
- function -= 1;
- function &= 1;
- function <<= S3C2410_GPIO_OFFSET(pin);
- } else {
- /*根據(jù)function確定2個(gè)bits的值*/
- function &= 3;
- /*將function設(shè)置到相應(yīng)的位置,此處是簡(jiǎn)單的位操作*/
- function <<= S3C2410_GPIO_OFFSET(pin)*2;
- }
- }
- /* modify the specified register wwith IRQs off */
/*寫操作都是先讀再寫*/
- /*保存中斷*/
- local_irq_save(flags);
- /*讀寄存器,base是寄存器基地址,而0x0是表示第一個(gè)寄存器GPBCON*/
- con = __raw_readl(base + 0x00);
- /*清零對(duì)應(yīng)的位*/
- con &= ~mask;
- /*設(shè)置相應(yīng)的位為對(duì)應(yīng)的功能*/
- con |= function;
- /*寫入寄存器*/
- __raw_writel(con, base + 0x00);
- /*恢復(fù)中斷*/
- local_irq_restore(flags);
- }
/*寫端口的數(shù)據(jù)寄存器,也就是GPxDAT的某一端口*/
- void s3c2410_gpio_setpin(unsigned int pin, unsigned int to)
- {
- /*得到端口所在塊的寄存器基地址*/
- void __iomem *base = S3C24XX_GPIO_BASE(pin);
- /*得到端口在所在寄存器中具體的偏移量*/
- unsigned long offs = S3C2410_GPIO_OFFSET(pin);
- unsigned long flags;
- unsigned long dat;
- /*中斷保存*/
- local_irq_save(flags);
- /*讀寄存器GPxDAT,base是所在塊寄存器的基地址,0x4是當(dāng)前寄存器的偏移量*/
- dat = __raw_readl(base + 0x04);
- /*清零該端口當(dāng)前的數(shù)據(jù)*/
- dat &= ~(1 << offs);
- /*一般是保存在另一個(gè)值中*/
- dat |= to << offs;
- __raw_writel(dat, base + 0x04);
- local_irq_restore(flags);
- }
- /*關(guān)于推到地址的方法按照上面的分布圖對(duì)照分析可能比較方便*/
- #ifdef CONFIG_CPU_S3C2400
- #define S3C24XX_GPIO_BASE(x) S3C2400_GPIO_BASE(x)
- #define S3C24XX_MISCCR S3C2400_MISCCR
- #else
- #define S3C24XX_GPIO_BASE(x) S3C2410_GPIO_BASE(x)
- #define S3C24XX_MISCCR S3C24XX_GPIOREG2(0x80)
- #endif
- #define S3C2410_GPIO_BASE(pin) ((((pin) & ~31) >> 1) + S3C24XX_VA_GPIO)
- #define S3C2410_GPIO_OFFSET(pin) ((pin) & 31)
- /*GPIO的物理地址起始*/
- #define S3C2410_PA_GPIO (0x56000000)
- /*GPIO的虛擬地址起始,實(shí)現(xiàn)的方法是在兩個(gè)物理地間隔加上UART 虛擬地址的起始*/
- #define S3C24XX_VA_GPIO ((S3C24XX_PA_GPIO - S3C24XX_PA_UART) + S3C24XX_VA_UART)
- /*大小*/
- #define S3C24XX_SZ_GPIO SZ_1M
- #define S3C24XX_VA_UART S3C_VA_UART
- #define S3C2410_PA_UART (0x50000000)
- #define S3C24XX_SZ_UART SZ_1M
/*映射的虛擬地址的固定起始地址*/
#define S3C_ADDR_BASE (0xF4000000)
- #ifndef __ASSEMBLY__
- #define S3C_ADDR(x) ((void __iomem __force *)S3C_ADDR_BASE + (x))
- #else
- #define S3C_ADDR(x) (S3C_ADDR_BASE + (x))
- #endif
- #define S3C_VA_IRQ S3C_ADDR(0x00000000) /* irq controller(s) */
- #define S3C_VA_SYS S3C_ADDR(0x00100000) /* system control */
- #define S3C_VA_MEM S3C_ADDR(0x00200000) /* system control */
- #define S3C_VA_TIMER S3C_ADDR(0x00300000) /* timer block */
- #define S3C_VA_WATCHDOG S3C_ADDR(0x00400000) /* watchdog */
- #define S3C_VA_UART S3C_ADDR(0x01000000) /* UART */
- static unsigned int __raw_readl(unsigned int ptr)
- {
- /*volatile表示ptr中的值是易變性的,*((volatile unsigned int *)ptr)是對(duì)地址取值*/
- return *((volatile unsigned int *)ptr);
- }
- static void __raw_writel(unsigned int value, unsigned int ptr)
- {
- /*((volatile unsigned int *)ptr)是對(duì)地址賦值*/
- *((volatile unsigned int *)ptr) = value;
- }
映射虛擬地址的關(guān)系建議自己繪圖,可能更加的直觀,快捷。在這段代碼中的兩個(gè)宏語(yǔ)句是比較難以理解的。這兩句宏定義充分利用了位操作的優(yōu)勢(shì)。
/*求端口所在塊寄存器的起始虛擬地址*/
#define S3C2410_GPIO_BASE(pin) ((((pin) & ~31) >> 1) + S3C24XX_VA_GPIO)
/*求端口在所在塊的偏移量*/
#define S3C2410_GPIO_OFFSET(pin) ((pin) & 31)
我們加以分析。我們以GPB5、GPA5作為分析對(duì)象。
GPB5的端口ID號(hào)是32*(2-1)+5=37,GPA5的端口ID號(hào)是32*(1-1)+5=5;
根據(jù)上面的圖可知,S3C24XX_VA_GPIO的值為0xFB000000.
S3C2410_GPIO_BASE(GPB5) = ((37&~31)>>1)+0xFB000000=0xFB000010 (剛好對(duì)應(yīng)于GPBCON的虛擬地址)
S3C2410_GPIO_BASE(GPA5) = ((5&~31)>>1)+0xFB000000=0xFB000000 (剛好對(duì)應(yīng)于GPBACON的虛擬地址)
也就是相當(dāng)于得到每一塊IO口的基地址也就是GPA,GPB,GPC,...等對(duì)應(yīng)的地址。
S3C2410_GPIO_OFFSET(GPB5) = 37 & 31 = 5;相當(dāng)于求32的余數(shù),也就是得到端口在所在塊中的偏移量。
LED的驅(qū)動(dòng)很簡(jiǎn)單,該驅(qū)動(dòng)是按著ioctl實(shí)現(xiàn)的一般步驟實(shí)現(xiàn)的,具有延續(xù)性。