|
http://blog.chinaunix.net/u1/49924/showart_499156.html 二、s3c2410fb_probe函數(shù)分析2.1 驅(qū)動(dòng)的入口點(diǎn)擺在面前的第一個(gè)問題相信應(yīng)該是,這個(gè)函數(shù)是從那里開始運(yùn)行的。這里就應(yīng)該從long long ago 開始了,打開drivers/video/s3c2410fb.c文件,然后找到s3c2410fb_init函數(shù),先不管它里面是怎么回事,再把目光下移就會看到這樣一串字符串module_init(s3c2410fb_init),郁悶,這和S3C2410fb_probe有啥關(guān)系嘛?這個(gè)問題問的好!不要著急慢慢往下面走。先摸摸module_init是何方神圣再說,于是乎我就登陸了http://lxr.linux.no/linux+v2.6.20/網(wǎng)站,在上面一搜,原來module_init老家在include/linux/init.h,原來它居然還有兩重身份,其原型如下:#ifndef MODULE……#define module_init(x) __initcall(x); ①……#else……#define module_init(initfn) \ ② static inline initcall_t __inittest(void) \ { return initfn; } \ int init_module(void) __attribute__((alias(#c)));……#endif 從上面可以看出,module_init到底用哪個(gè),就取決于MODULE了,那么MODULE的作用是什么呢?我們知道Linux可以將設(shè)備當(dāng)作模塊動(dòng)態(tài)加進(jìn)內(nèi)核,也可以直接編譯進(jìn)內(nèi)核,說到這里大概有點(diǎn)明白MODULE的作用了,不錯(cuò)!它就是要控制一個(gè)驅(qū)動(dòng)加入內(nèi)核的方式。定義了MODULE就表示將設(shè)備當(dāng)作模塊動(dòng)態(tài)加入。所以上面的①表示將設(shè)備加進(jìn)內(nèi)核。在②中的__attribute__((alias(#initfn)))很有意思,這代表什么呢?主要alias就是屬性的意思,它的英文意思是別名,可以在http://publib.boulder.ibm.com/infocenter/lnxpcomp/v8v101/index.jsp?topic=/com.ibm.xlcpp8l.doc/language/ref/fn_attrib_alias.htm找到它的詳細(xì)說明,這里簡單的說int init_module(void) __attribute__((alias(#initfn)));的意思為init_module是initfn的別名,或者init_module是initfn的一個(gè)連接,再簡單一點(diǎn)說這個(gè)時(shí)候module_init宏基因突變成了init_module()了。對于第一種情況,__initcall(fn) 又被宏定義成了device_initcall(fn),也就是說module_init(x)等于device_initcall(fn)。對于device_initcall(fn)又是一個(gè)宏定義,它被定義成了__define_initcall("6",fn,6),至于這個(gè)宏表示什么意思,在這里就不啰嗦重復(fù)了,在Linux-2.6.20的cs8900驅(qū)動(dòng)分析(一)這篇文章中有對它的揭秘。 上面啰嗦了這么多,最終是要說明只要用module_init申明了一個(gè)函數(shù),該函數(shù)就會被Linux內(nèi)核在適當(dāng)?shù)臅r(shí)機(jī)運(yùn)行,這些時(shí)機(jī)包括在linux啟動(dòng)的do_initcalls()時(shí)調(diào)用(設(shè)備被編譯進(jìn)內(nèi)核),或者在動(dòng)態(tài)插入時(shí)調(diào)用。 回到上面的module_init(s3c2410fb_init)處,也就是說內(nèi)核與buffer驅(qū)動(dòng)發(fā)生關(guān)系的第一次地點(diǎn)是在s3c2410fb_init函數(shù),該函數(shù)就只有一條語句platform_driver_register (&s3c2410fb_driver);??????…… 2.2 platform是何許人也 platform可以理解成一種設(shè)備類型,就像字符設(shè)備、塊設(shè)備和網(wǎng)絡(luò)設(shè)備一樣,而LCD就屬于這種設(shè)備。對于platform設(shè)備Linux為應(yīng)用添加了相關(guān)的接口,在這里只是簡單的說說這些接口的用法,而不去深入探討這些接口的實(shí)現(xiàn)(我現(xiàn)在還沒有那個(gè)能力呢。。說到這里,馬上就有個(gè)問題涌上心頭了,那就是Linux提供了那些接口呢?如果我們需要添加這些設(shè)備應(yīng)該怎么樣做呢? platform中的相關(guān)數(shù)據(jù)結(jié)構(gòu)是應(yīng)用的關(guān)鍵,為了向內(nèi)核添加一個(gè)platform設(shè)備,程序員應(yīng)該填寫兩個(gè)數(shù)據(jù)結(jié)構(gòu)platform_device 和platform_driver,這兩個(gè)數(shù)據(jù)結(jié)構(gòu)的定義都可以在include/linux/platform_device.h文件中找到。看看LCD驅(qū)動(dòng)是怎么做的,第一步是填寫platform_device,在arch/arm/mach-s3c2410/devs.c可以找到填寫platform_device的代碼,如下:static u64 s3c_device_lcd_dmamask = 0xffffffffUL;struct platform_device s3c_device_lcd = { .name = "s3c2410-lcd", .id = -1, .num_resources = ARRAY_SIZE (s3c_lcd_resource), .resource = s3c_lcd_resource, .dev = { .dma_mask = &s3c_device_lcd_dmamask, .coherent_dma_mask = 0xffffffffUL }}; 這里面的各個(gè)數(shù)據(jù)成員的意思,在platform_device數(shù)據(jù)結(jié)構(gòu)中有詳細(xì)的說明,這里不贅述。上面的代碼中的ARRAY_SIZE宏還是比較有意思的,其實(shí)是個(gè)c的編程技巧,這個(gè)技巧很有用哦!可以在include/linux/kernel.h中找到它的定義:#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))該宏可以方便的求出一個(gè)數(shù)組中有多少數(shù)據(jù)成員,這在很多情況下是很有用的,比如對于 int a[]={1,5,65,23,12,20,3}數(shù)組,可以使用該宏求出a[]有7個(gè)元素。 另外,platform_device的另外一項(xiàng)重要成員是resource,在上面的代碼中此域被賦予了s3c_lcd_resource,s3c_lcd_resource也可以在arch/arm/mach-s3c2410/devs.c找到。static struct resource s3c_lcd_resource[] = { [0] = { .start = S3C24XX_PA_LCD, .end = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1, .flags = IORESOURCE_MEM, }, [1] = { .start = IRQ_LCD, .end = IRQ_LCD, .flags = IORESOURCE_IRQ, }};struct resource結(jié)構(gòu)實(shí)際上描述了該設(shè)備占用的硬件資源(如地址空間,中斷號等s),s3c_lcd_resource描述了內(nèi)存空間和中斷分配情況。 最后在smdk2410_devices指針數(shù)組中添加上s3c_device_lcd的大名,Linux在初始化platform的時(shí)候就知道系統(tǒng)中有個(gè)s3c_device_lcd設(shè)備了。注意了這里只是向Linux描述了設(shè)備需要的資源情況,不代表內(nèi)核會給這些資源的。如果設(shè)備要得到這些設(shè)備還需要在自己的初始化函數(shù)中去申請。 static struct platform_device *smdk2410_devices[] __initdata = { &s3c_device_usb, &s3c_device_lcd, &s3c_device_wdt, &s3c_device_i2c, &s3c_device_iis, &s3c_device_ts,};說到這里,應(yīng)該說向Linux添加一個(gè)platform設(shè)備應(yīng)該很容易。 2.2 回到s3c2410fb_init終于把platform的相關(guān)知識啰嗦了一番,下面回到s3c2410fb_init函數(shù)所調(diào)用platform_driver_register(&s3c2410fb_driver)。簡單地說platform_driver_register要將向內(nèi)核注冊一個(gè)platform設(shè)備的驅(qū)動(dòng),這里是要注冊LCD設(shè)備。上面說過platform有兩個(gè)重要的數(shù)據(jù)結(jié)構(gòu)platform_device和platform_driver,現(xiàn)在是應(yīng)該提到后者的時(shí)候了。platform_driver也在include/linux/platform_device.h中,它的各個(gè)成員應(yīng)該再明白不過來吧!在LCD驅(qū)動(dòng)程序(drivers/video/s3c2410fb.c)中定義了填充了platform_driver這個(gè)結(jié)構(gòu),如下:static struct platform_driver s3c2410fb_driver = { .probe = s3c2410fb_probe, .remove = s3c2410fb_remove, .suspend = s3c2410fb_suspend, .resume = s3c2410fb_resume, .driver = { .name = "s3c2410-lcd", .owner = THIS_MODULE, },};可以看到該platform設(shè)備的驅(qū)動(dòng)函數(shù)有s3c2410fb_probe、s3c2410fb_remove等等。通過platform_driver_register函數(shù)注冊該設(shè)備的過程中,它會回調(diào).probe函數(shù),說到這里也就明白s3c2410fb_probe是在platform_driver_registe中回調(diào)的。到目前為止,經(jīng)過二萬五千里長征終于到達(dá)s3c2410fb_probe(LCD的驅(qū)動(dòng)程序)了。 2.3 s3c2410fb_probe揭秘 對于該函數(shù),我想最好的辦法就是跟著程序一步一步的解釋。OK,let’s go to ……static int __init s3c2410fb_probe(struct platform_device *pdev){ struct s3c2410 fb_info *info; //s3c2410fb_info結(jié)構(gòu)在driver/video/s3c2410fb.h中定義,//可以說該結(jié)構(gòu)記錄了s3c2410fb驅(qū)動(dòng)的所有信息。 struct fb_info *fbinfo; /* fb_info為內(nèi)核提供的buffer驅(qū)動(dòng)的接口數(shù)據(jù)結(jié)構(gòu), 每個(gè)幀緩沖驅(qū)動(dòng)都對應(yīng)一個(gè)這樣的結(jié)構(gòu)。s3c2410fb_probe的最終目的填充該結(jié)構(gòu),并向內(nèi)核注冊。*/ struct s3c2410fb_hw *mregs; // s3c2410fb_hw為描述LCD的硬件控制寄存器的結(jié)構(gòu)體,//在include/asm-arm/arch-s3c2410/fb.h可以找到它的原型。…… mach_info = pdev->dev.platform_data; /*這一步看來要多費(fèi)些口舌了。mach_info是一個(gè)s3c2410fb_mach_info類型的指針,注意區(qū)分s3c2410fb_mach_info和s3c2410fb_info結(jié)構(gòu),簡單地說前者只是用于描述LCD初始化時(shí)所用的值,而后者是描述整個(gè)LCD驅(qū)動(dòng)的結(jié)構(gòu)體。s3c2410fb_mach_info在include/asm-arm/arch-s3c2410/fb.h中定義,從他的位置可以看出它和平臺相關(guān),也即它不是內(nèi)核認(rèn)知的數(shù)據(jù)結(jié)構(gòu),這只是驅(qū)動(dòng)程序設(shè)計(jì)者設(shè)計(jì)的結(jié)構(gòu)。這里的主要疑問是什么呢?從下面的if語句可以看出如果mach_info等于NULL的話,整個(gè)驅(qū)動(dòng)程序就退出了,這就引出了問題――pdev->dev.platform_data是在什么時(shí)候被初始化的呢?看來要回答這個(gè)問題,歷史應(yīng)該回到孫悟空大鬧天宮的時(shí)候了。按住倒帶鍵不放一直到本篇文章的第一部分,看看那個(gè)時(shí)候做了些什么。放在這里來解釋第一部分的內(nèi)容希望沒有為時(shí)已晚。其實(shí)在內(nèi)核啟動(dòng)init進(jìn)程之前就會執(zhí)行smdk2410_map_io( )函數(shù)(內(nèi)核的啟動(dòng)分析就免了吧@_@),而在smdk2410_map_io( )中我們加入了s3c24xx_fb_set_platdata (&smdk2410_lcd_platdata);這條語句,s3c24xx_fb_set_platdata()的實(shí)現(xiàn)為:void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd){ s3c_device_lcd.dev.platform_data = pd;}根據(jù)這些代碼,可以清楚的看到s3c_device_lcd.dev.platform_data指向了smdk2410_lcd_platdata,而這個(gè)smdk2410_lcd_platdata就是一個(gè)s3c2410fb_mach_info的變量,它里面就存放了LCD驅(qū)動(dòng)初始化需要的初始數(shù)據(jù)。當(dāng)s3c2410fb_probe被回調(diào)時(shí),所傳給它的參數(shù)實(shí)際就是s3c_device_lcd的首地址,說到這里一切應(yīng)該都明了了吧!好了,又撤了一通,現(xiàn)在假設(shè)這步成功,繼續(xù)往下面走。*/ if (mach_info == NULL) { dev_err(&pdev->dev,"no platform data for lcd, cannot attach\n"); return -EINVAL; } mregs = &mach_info->regs; //mregs指向硬件各控制寄存器的初始值,可參見第一部//分的smdk2410_lcd_platdata變量。 irq = platform_get_irq(pdev, 0); /*該函數(shù)獲得中斷號,該函數(shù)的實(shí)現(xiàn)是通過比較struct resource的flags域,得到irq中斷號,在上2.1的時(shí)候提到s3c_lcd_resource[],platform_get_irq函數(shù)檢測到flags==IORESOURCE_IRQ時(shí)就返回中斷號IRQ_LCD。詳細(xì)的內(nèi)容請讀它的源代碼吧!*/ if (irq < 0) { //沒有找到可用的中斷號,返回-ENOENT dev_err(&pdev->dev, "no irq for device\n"); return -ENOENT; } fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev); /* framebuffer_alloc可以在include/linux/fb.h文件中找到其原型:struct fb_info *framebuffer_alloc(size_t size, struct device *dev); 它的功能是向內(nèi)核申請一段大小為sizeof(struct fb_info) + size的空間,其中size的大小代表設(shè)備的私有數(shù)據(jù)空間,并用fb_info的par域指向該私有空間。*/ if (!fbinfo) { return -ENOMEM; } //以下開始做正經(jīng)事了,填充fbinfo了。 info = fbinfo->par; //你中有我,我中有你! info->fb = fbinfo; platform_set_drvdata(pdev, fbinfo); /*該函數(shù)的實(shí)現(xiàn)非常簡單,實(shí)際的操作為:pdev->dev.driver_data = fbinfo,device結(jié)構(gòu)的driver_data域指向驅(qū)動(dòng)程序的私有數(shù)據(jù)空間。*/ dprintk("devinit\n"); strcpy(fbinfo->fix.id, driver_name); memcpy(&info->regs, &mach_info->regs, sizeof(info->regs)); /* Stop the video and unset ENVID if set */ info->regs.lcdcon1 &= ~S3C2410_LCDCON1_ENVID; lcdcon1 = readl(S3C2410_LCDCON1); writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1);//停止硬件 /*以下的對fbinfo的填寫就免了吧!對于fb_info結(jié)構(gòu)的各個(gè)成員,在include/linux/fb文件中都有詳細(xì)的說明,如果不知道說明的意思,就應(yīng)該找些基本的知識讀讀了。在眾多的初始化中,fbinfo->fbops = &s3c2410fb_ops;是值得一提的,變量s3c2410fb_ops 就在s3c2410fb.c中定義,它記錄了該幀緩沖區(qū)驅(qū)動(dòng)所支持的操作 */ …… for (i = 0; i < 256; i++) //初始化調(diào)色板緩沖區(qū) info->palette_buffer[i] = PALETTE_BUFF_CLEAR; if (!request_mem_region((unsigned long)S3C24XX_VA_LCD, SZ_1M, "s3c2410-lcd")) {/* 向內(nèi)核申請內(nèi)存空間,如果request_mem_region返回0表示申請失敗,此時(shí)程序跳到dealloc_fb處開始執(zhí)行,該處會調(diào)用framebuffer_release釋放剛才由framebuffer_alloc申請的fb_info空間 */ ret = -EBUSY; goto dealloc_fb; }…… ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info);/* 向內(nèi)核注冊中斷,如果注冊失敗,程序跳轉(zhuǎn)到release_mem處運(yùn)行,此處釋放fb_info和剛才由request_mem_region申請的內(nèi)存空間 */ if (ret) { dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret); ret = -EBUSY; goto release_mem; } info->clk = clk_get(NULL, "lcd"); //該函數(shù)得到時(shí)鐘源,并與硬件緊密相連,對于我的//板子,可以在arch/arm/mach-s3c2410/clock.c看到它的原型和實(shí)現(xiàn)。 if (!info->clk || IS_ERR(info->clk)) { printk(KERN_ERR "failed to get lcd clock source\n"); ret = -ENOENT; goto release_irq; //該處釋放上面申請的fb_info,內(nèi)存,和irq資源 } clk_enable(info->clk); //打開時(shí)鐘 dprintk("got and enabled clock\n"); msleep(1); //運(yùn)行得太久有點(diǎn)累了,去打個(gè)盹再說 /* Initialize video memory */ ret = s3c2410fb_map_video_memory(info);/*此函數(shù)就在s3c2410fb.c文件中被定義,它的作用是申請幀緩沖器內(nèi)存空間*/ if (ret) { printk( KERN_ERR "Failed to allocate video RAM: %d\n", ret); ret = -ENOMEM; goto release_clock; //釋放所有已得到的資源 } dprintk("got video memory\n"); ret = s3c2410fb_init_registers(info); //此函數(shù)也在s3c2410fb.c文件中定義,后面會分析 ret = s3c2410fb_check_var(&fbinfo->var, fbinfo); //此函數(shù)也在s3c2410fb.c文件中定義 ret = register_framebuffer(fbinfo); //神圣的時(shí)刻終于到來,向內(nèi)核正式注冊。 if (ret < 0) { printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret); goto free_video_memory; //不讓注冊真郁悶,那就釋放所有的資源,出家算了! } /* create device files */ device_create_file(&pdev->dev, &dev_attr_debug); //為該設(shè)備創(chuàng)建一個(gè)在sysfs中的屬性 printk(KERN_INFO "fb%d: %s frame buffer device\n", fbinfo->node, fbinfo->fix.id); return 0; //大功告成! free_video_memory: s3c2410fb_unmap_video_memory(info);release_clock: clk_disable(info->clk); clk_put(info->clk);release_irq: free_irq(irq,info);release_mem: release_mem_region((unsigned long)S3C24XX_VA_LCD, S3C24XX_SZ_LCD);dealloc_fb: framebuffer_release(fbinfo); return ret;} To be continued…… ------ anmnmnly ------ 2008.03.18 |
|