添加内核驱动模块(1)(mydriver.c+ Konfig+Makefile )
(1)編寫驅動程序代碼
首先是分配代碼各個區域。
第一個是頭文件區域。
然后是宏定義區域。
然后是數據定義區域。
然后是操作函數區。
然后是注冊函數區。
然后是修飾代碼區。
首先編寫修飾代碼
MODULE_AUTHOR("liuzheng"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION(DRIVER_NAME": PmodOLED driver"); MODULE_ALIAS(DRIVER_NAME);為此我們定義一個宏DRIVER_NAME,
#define DRIVER_NAME "pmodoled-gpio"然后加入頭文件。
加入linux常規需要的頭文件。
加入編寫module需要的頭文件。
#include <linux/module.h>#include <linux/platform_device.h>#include <linux/cdev.h>加入編寫open firmware需要的頭文件。
#include <linux/of.h>#include <linux/of_address.h>#include <linux/of_gpio.h>加入編寫操作硬件函數需要的頭文件。
#include <linux/gpio.h> #include <linux/spi/spi.h> #include <linux/spi/spi_gpio.h>然后定義DDB。描述塊數據結構,用于封裝相關數據和操作函數。
struct gpio_pmodoled_device {char *name;/* R/W Mutex Lock */struct mutex mutex;/* Display Buffers */uint8_t disp_on;uint8_t *disp_buf;/* Pin Assignment */unsigned long iVBAT;unsigned long iVDD;unsigned long iRES;unsigned long iDC;unsigned long iSCLK;unsigned long iSDIN;unsigned long iCS;/* SPI Info */uint32_t spi_id;/* platform device structures */struct platform_device *pdev;/* Char Device */struct cdev cdev;struct spi_device *spi;dev_t dev_id; };定義了name,指向一個字符串,描述這個設備名。
定義了mutex,用來上鎖,防止多進程操作時不能互斥。
定義了disp_on,用來指示狀態。
定義了disp_buf,指向一個顯示緩沖區。
定義了 iVBAT;iVDD;iRES;iDC;iSCLK;iSDIN;iCS;用來表示引腳號。
定義了spi_id,指示SPI的設備ID。
定義了pdev,指向一個平臺設備描述塊。
定義了cdev,是本設備的內含描述塊。說明本設備是一個擴展的cdev。
定義了spi,指向一個SPI設備描述塊。
定義了dev_id,用來表示本設備的設備ID。
既然是編寫CDEV,那么就遵循CDEV的一般編寫步驟。
先編寫驅動框架。
定義了一個靜態變量,是一個pdev的描述塊。
賦值了probe函數,指向gpio_pmodoled_of_prob。
賦值了remove函數,指向gpio_pmodoled_of_remove。
賦值了driver結構。
成員owner賦值為THIS_MODULE。
成員name賦值為DRIVER_NAME。
成員of_match_table賦值為gpio_pmodoled_of_match
然后,注冊到系統中。申請系統資源,為驅動分配一個描述塊PDDB。
之前,我們申請PDDB時,用到了一個OFDID類型的數組,我們來定義。
static const struct of_device_id gpio_pmodoled_of_match[] __devinitconst = {{ .compatible = "liuzheng,pmodoled-gpio", },{}, }; MODULE_DEVICE_TABLE(of, gpio_pmodoled_of_match);定義了一個靜態數組,成員類型是OFDID。
不允許修改,所以限定了const。
是在devinit階段使用的,所以限定了__devinitconst。
數組中,others成員,用空成員賦值。這個空成員是必須的,這是OF的要求。
然后注冊到系統中。申請系統資源,申明這是一個OFDID類型的數組。
下面我們來實現我們剛才定義的probe函數和remove函數。
static int __devinit gpio_pmodoled_of_probe(struct platform_device *pdev) {struct gpio_pmodoled_device *gpio_pmodoled_dev;struct platform_device *gpio_pmodoled_pdev;struct spi_gpio_platform_data *gpio_pmodoled_pdata;struct device_node *np = pdev->dev.of_node;const u32* tree_info;int status = 0;/* Alloc Space for platform device structure */gpio_pmodoled_dev = (struct gpio_pmodoled_device*) kzalloc(sizeof(*gpio_pmodoled_dev), GFP_KERNEL);if(!gpio_pmodoled_dev) {status = -ENOMEM;goto dev_alloc_err;}/* Alloc Graphic Buffer for device */gpio_pmodoled_dev->disp_buf = (uint8_t*) kmalloc(DISPLAY_BUF_SZ, GFP_KERNEL);if(!gpio_pmodoled_dev->disp_buf) {status = -ENOMEM;dev_err(&pdev->dev, "Device Display data buffer allocation failed: %d\n", status);goto disp_buf_alloc_err;}/* Get the GPIO Pins */gpio_pmodoled_dev->iVBAT = of_get_named_gpio(np, "vbat-gpio", 0);gpio_pmodoled_dev->iVDD = of_get_named_gpio(np, "vdd-gpio", 0);gpio_pmodoled_dev->iRES = of_get_named_gpio(np, "res-gpio", 0); gpio_pmodoled_dev->iDC = of_get_named_gpio(np, "dc-gpio", 0); gpio_pmodoled_dev->iSCLK = of_get_named_gpio(np, "spi-sclk-gpio", 0); gpio_pmodoled_dev->iSDIN = of_get_named_gpio(np, "spi-sdin-gpio", 0); status = of_get_named_gpio(np, "spi-cs-gpio", 0);gpio_pmodoled_dev->iCS = (status < 0) ? SPI_GPIO_NO_CHIPSELECT : status; #ifdef CONFIG_PMODS_DEBUG printk(KERN_INFO DRIVER_NAME " %s: iVBAT: 0x%lx\n", np->name, gpio_pmodoled_dev->iVBAT);printk(KERN_INFO DRIVER_NAME " %s: iVDD : 0x%lx\n", np->name, gpio_pmodoled_dev->iVDD);printk(KERN_INFO DRIVER_NAME " %s: iRES : 0x%lx\n", np->name, gpio_pmodoled_dev->iRES);printk(KERN_INFO DRIVER_NAME " %s: iDC : 0x%lx\n", np->name, gpio_pmodoled_dev->iDC);printk(KERN_INFO DRIVER_NAME " %s: iSCLK: 0x%lx\n", np->name, gpio_pmodoled_dev->iSCLK);printk(KERN_INFO DRIVER_NAME " %s: iSDIN: 0x%lx\n", np->name, gpio_pmodoled_dev->iSDIN);printk(KERN_INFO DRIVER_NAME " %s: iCS : 0x%lx\n", np->name, gpio_pmodoled_dev->iCS); #endif/* Get SPI Related Params */tree_info = of_get_property(np, "spi-bus-num", NULL);if(tree_info) {gpio_pmodoled_dev->spi_id = be32_to_cpup((tree_info)); #ifdef CONFIG_PMODS_DEBUGprintk(KERN_INFO DRIVER_NAME " %s: BUS_ID\t%x\n", np->name, gpio_pmodoled_dev->spi_id); #endif}/* Alloc Space for platform data structure */gpio_pmodoled_pdata = (struct spi_gpio_platform_data*) kzalloc(sizeof(*gpio_pmodoled_pdata), GFP_KERNEL);if(!gpio_pmodoled_pdata) {status = -ENOMEM;goto pdata_alloc_err;}/* Fill up Platform Data Structure */gpio_pmodoled_pdata->sck = gpio_pmodoled_dev->iSCLK;gpio_pmodoled_pdata->miso = SPI_GPIO_NO_MISO;gpio_pmodoled_pdata->mosi = gpio_pmodoled_dev->iSDIN;gpio_pmodoled_pdata->num_chipselect = 1;/* Alloc Space for platform data structure */gpio_pmodoled_pdev = (struct platform_device*) kzalloc(sizeof(*gpio_pmodoled_pdev), GFP_KERNEL);if(!gpio_pmodoled_pdev) {status = -ENOMEM;goto pdev_alloc_err;}/* Fill up Platform Device Structure */ gpio_pmodoled_pdev->name = "spi_gpio";gpio_pmodoled_pdev->id = gpio_pmodoled_dev->spi_id;gpio_pmodoled_pdev->dev.platform_data = gpio_pmodoled_pdata;gpio_pmodoled_dev->pdev = gpio_pmodoled_pdev;/* Register spi_gpio master */status = platform_device_register(gpio_pmodoled_dev->pdev);if(status < 0) {dev_err(&pdev->dev, "platform_device_register failed: %d\n", status);goto pdev_reg_err;}#ifdef CONFIG_PMODS_DEBUGprintk(KERN_INFO DRIVER_NAME " %s: spi_gpio platform device registered.\n", np->name); #endifgpio_pmodoled_dev->name = np->name; /* Fill up Board Info for SPI device */status = add_gpio_pmodoled_device_to_bus(gpio_pmodoled_dev);if(status < 0) {dev_err(&pdev->dev, "add_gpio_pmodoled_device_to_bus failed: %d\n", status);goto spi_add_err;}#ifdef CONFIG_PMODS_DEBUGprintk(KERN_INFO DRIVER_NAME " %s: spi device registered.\n", np->name); #endif/* Point device node data to gpio_pmodoled_device structure */if(np->data == NULL)np->data = gpio_pmodoled_dev;if(gpio_pmodoled_dev_id == 0) {/* Alloc Major & Minor number for char device */status = alloc_chrdev_region(&gpio_pmodoled_dev_id, 0, MAX_PMODOLED_GPIO_DEV_NUM, DRIVER_NAME);if(status) {dev_err(&pdev->dev, "Character device region not allocated correctly: %d\n", status);goto err_alloc_chrdev_region;} #ifdef CONFIG_PMODS_DEBUGprintk(KERN_INFO DRIVER_NAME " : Char Device Region Registered, with Major: %d.\n", MAJOR(gpio_pmodoled_dev_id)); #endif}if(gpio_pmodoled_class == NULL) {/* Create Pmodoled-gpio Device Class */gpio_pmodoled_class = class_create(THIS_MODULE, DRIVER_NAME);if (IS_ERR(gpio_pmodoled_class)) {status = PTR_ERR(gpio_pmodoled_class);goto err_create_class;} #ifdef CONFIG_PMODS_DEBUGprintk(KERN_INFO DRIVER_NAME " : pmodoled_gpio device class registered.\n"); #endif}if(spi_drv_registered == 0) {/* Register SPI Driver for Pmodoled Device */status = spi_register_driver(&gpio_pmodoled_spi_driver);if (status < 0) {dev_err(&pdev->dev, "gpio_pmodoled_spi_driver register failed: %d\n", status);goto err_spi_register;}spi_drv_registered = 1;}device_num ++;return status; err_spi_register:class_destroy(gpio_pmodoled_class);gpio_pmodoled_class = NULL; err_create_class:unregister_chrdev_region(gpio_pmodoled_dev_id, MAX_PMODOLED_GPIO_DEV_NUM);gpio_pmodoled_dev_id = 0; err_alloc_chrdev_region:spi_unregister_device(gpio_pmodoled_dev->spi); spi_add_err:platform_device_unregister(gpio_pmodoled_dev->pdev); pdev_reg_err:kfree(gpio_pmodoled_pdev); pdev_alloc_err:kfree(gpio_pmodoled_pdata); pdata_alloc_err:kfree(gpio_pmodoled_dev->disp_buf); disp_buf_alloc_err:kfree(gpio_pmodoled_dev); dev_alloc_err:return status; }當syscall調用probe函數時,會傳遞給probe一個PDEV類型的指針pdev。
首先分配并初始化一系列指針,方便引用各個數據結構,各個描述塊。
利用pdev找到它的內含成員dev,這個成員中有一個內含成員of_node,它是一個指向device_node類型的描述塊的指針。我們把它賦值給np。后續我們直接使用np操作device_node。
在kernelzone中,分配資源,大小用sizeof()編譯控制字確定,(sizeof特性是編譯期間確定,而不是運行期間確定。)并將返回的void指針強制轉換成struct gpio_pmodoled_device型指針。賦值給gpio_pmodoled_dev。后續我們直接使用gpio_pmodoled_dev這個句柄。
判斷返回的指針是否合法。
如果為NULL,則為非法指針。
設置status,并進入退出處理棧dev_alloc_err。
如果不為NULL,是合法指針,繼續進行。
在kernelmem中,分配資源,大小用宏確定。
這里我們定義了宏,
將返回的void指針強制轉換成char型指針。并賦值給gpio_pmodoled_dev->disp_buf。
判斷返回的指針是否合法。
如果為NULL,則為非法指針。
設置status,打印錯誤信息,并進入退出處理棧disp_buf_alloc_err。
如果不為NULL,是合法指針,繼續進行。
利用剛才獲得的device_node,通過name匹配,獲取GPIO編號。
of_get_named_gpio()是OF_GPIOLIB庫函數,它找到GPIO編號,并返回編號值。
將找到的GPIO的編號值,賦值給PMOD的各個成員。
iVBAT,iVDD,iRES,iDC,iSCLK,iSDIN,iCS。
在此處定義一些調試代碼,并用編譯配置宏進行ifdef處理。
printk()打印級別是KERN_INFO。
利用剛才獲得的device_node,通過name匹配,獲取property。返回一個const char型的指針,這是只讀數據,所以我們也必須賦值給const char型指針tree_info。
判斷返回的指針的合法性。
如果為NULL,則為非法指針。由于不是致命錯誤,不用進入退出處理棧。
跳過步驟中的關聯操作,繼續執行。
如果是合法指針,則執行關聯操作。賦值給spi_id。
在kernelzone中,分配資源,大小用sizeof()編譯控制字確定。并將返回的void指針強制轉換成struct spi_gpio_platform_data型指針。賦值給gpio_pmodoled_pdata。后續我們直接使用gpio_pmodoled_pdata這個句柄。
判斷返回指針的合法性。
如果為NULL,則為非法指針。
設置status,打印錯誤信息,并進入退出處理棧pdata_alloc_err。
如果不為NULL,是合法指針,繼續進行。
填充之前的PDATA結構體。
gpio_pmodoled_pdata->sck,gpio_pmodoled_pdata->miso,gpio_pmodoled_pdata->mosi,gpio_pmodoled_pdata->num_chipselect
在kernelzone中,分配資源,大小用sizeof()編譯控制字確定。并將返回的void指針強制轉換成struct platform_device型指針。并賦值給gpio_pmodoled_pdev。后續我們直接使用這個句柄。
判斷返回指針的合法性。
如果為NULL,則為非法指針。
設置status,打印錯誤信息,并進入退出處理棧pdev_alloc_err。
如果不為NULL,是合法指針,繼續進行。
填充PDEV結構體。
gpio_pmodoled_pdev->name,gpio_pmodoled_pdev->id,gpio_pmodoled_pdev->dev.platform_data,
將PMOD和PDEV關聯起來。
gpio_pmodoled_dev->pdev = gpio_pmodoled_pdev;
注冊PDEV。將剛才申請并初始化的PDEV描述塊資源,注冊到內核中。
platform_device_register(),需要一個參數,是一個PDEV描述塊的指針。返回注冊狀態。
判斷返回的狀態的合法性。
如果非法,那么打印錯誤信息,并進入退出處理棧pdev_reg_err。
如果合法,那么繼續進行。
填充PMOD的name成員,將device_node的name成員,賦值給PMOD。
將PMOD描述塊注冊到BUS中。返回狀態值。
(這里用到了add_gpio_pmodoled_device_to_bus()函數,后面再講。)
判斷返回的狀態的合法性。
如果非法,打印錯誤信息,并進入退出處理棧spi_add_err。
如果合法,那么繼續執行。
填充device_node的data成員。
判斷data成員是否已經存在合法值。
如果不為NULL,說明已經有了合法值。跳過,不執行關聯操作。
如果為NULL,則執行關聯操作。為data成員賦值。將gpio_pmodoled_dev賦值給np->data。
我們定義了一個dev_t類型的全局變量gpio_pmodoled_dev_id。用來登記申請成功的PMOD的ID信息。初始值為0.
判斷gpio_pmodoled_dev_id的值的情況。
如果不為0,說明之前已經獲得ID。跳過,不執行關聯操作。
如果為0,說明之前沒有獲得過ID。那么執行關聯操作。
在系統中申請region資源。
alloc_chrdev_region()負責分配region資源,并將獲得的DEV_ID填入gpio_pmodoled_dev_id,并返回狀態值。
這里我們定義了宏,限定最大值。
#define MAX_PMODOLED_GPIO_DEV_NUM 16
判斷返回狀態的合法性。
如果非法,那么打印出錯信息,并進入退出處理棧err_alloc_chrdev_region。
如果合法,那么繼續執行。
我們定義了靜態變量gpio_pmodoled_class,它是一個struct class型的指針。指向一個CLASS描述塊。
判斷gpio_pmodoled_class的合法性。
如果不為NULL,說明之前已經向系統申請過CLASS資源。跳過,不執行關聯操作。
如果為NULL,說明之前沒有向系統申請過CLASS資源。那么執行關聯操作。
在系統中申請CLASS資源。
class_create()負責在系統中為module和drivername生成一個CLASS描述塊。并返回這個資源的指針。將返回的指針賦值給gpio_pmodoled_class。
判斷返回的指針的合法性。
如果是ERR指針,那么識別ERR代碼,并賦值給status。然后進入退出處理棧err_create_class。
如果不是ERR指針,那么繼續執行。
我們定義了靜態變量spi_drv_registered,用來記錄SPI DRIVER的注冊情況。
判斷spi_drv_registered的合法性。
如果不為NULL,說明之前已經在系統中注冊過SPIDRIVER,跳過,不執行關聯操作。
如果為NULL,說明之前沒有在系統中注冊過SPIDRIVER,那么執行關聯操作。
向系統中注冊SPIDRIVER。
spi_register_driver()負責向系統中注冊一個SPIDRIVER,它接收一個struct spi_driver型的描述塊的指針。返回狀態值。
判斷返回的狀態。
如果非法,那么打印錯誤信息,并進入退出處理棧err_spi_register。
如果合法,那么繼續執行。
修改spi_drv_registered,設置為已經注冊。
我們定義了靜態變量device_num,用來記錄系統中注冊成功的PMOD設備數
static unsigned int device_num = 0;
在PMOD成功注冊后,修改這個變量,記錄當前合法的設備數量。
device_num ++;
至此,設備probe全部完成。沒有發生錯誤,所以不進入退出處理棧。
直接return。
我們定義了靜態變量gpio_pmodoled_spi_driver。它是一個struct spi_driver型的結構體變量,不是指針。定義時進行了初始化。
static struct spi_driver gpio_pmodoled_spi_driver = {.driver = {.name = SPI_DRIVER_NAME,.bus = &spi_bus_type,.owner = THIS_MODULE,},.probe = gpio_pmodoled_spi_probe,.remove = __devexit_p(gpio_pmodoled_spi_remove), };成員probe賦值為gpio_pmodoled_spi_probe。
成員remove賦值為gpio_pmodoled_spi_remove。
成員driver中,
owner賦值為THIS_MODULE。
name賦值為SPI_DRIVER_NAME,這是一個宏,我們定義為
bus賦值為spi_bus_type的指針。這是一個全局變量,在SPILIB中定義。
再編寫remove函數。
static int gpio_pmodoled_of_remove(struct platform_device *pdev) {struct gpio_pmodoled_device *gpio_pmodoled_dev;struct device_node *np = pdev->dev.of_node;if(np->data == NULL) {dev_err(&pdev->dev, "pmodoled %s: ERROR: No gpio_pmodoled_device structure found!\n", np->name);return -ENOSYS;}gpio_pmodoled_dev = (struct gpio_pmodoled_device*) (np->data);#ifdef CONFIG_PMODS_DEBUGprintk(KERN_INFO DRIVER_NAME " %s : Free display buffer.\n", np->name); #endifif(gpio_pmodoled_dev->disp_buf != NULL) {kfree(gpio_pmodoled_dev->disp_buf);}#ifdef CONFIG_PMODS_DEBUGprintk(KERN_INFO DRIVER_NAME " %s : Unregister gpio_spi Platform Devices.\n", np->name); #endifif(gpio_pmodoled_dev->pdev != NULL) {platform_device_unregister(gpio_pmodoled_dev->pdev);}np->data = NULL;device_num--;/* Unregister SPI Driver, Destroy pmodoled-gpio class, Release device id Region after* all pmodoled-gpio devices have been removed.*/if(device_num == 0) { #ifdef CONFIG_PMODS_DEBUGprintk(KERN_INFO DRIVER_NAME " : Unregister SPI Driver.\n"); #endifspi_unregister_driver(&gpio_pmodoled_spi_driver);spi_drv_registered = 0;#ifdef CONFIG_PMODS_DEBUGprintk(KERN_INFO DRIVER_NAME " : Destroy pmodoled_gpio Class.\n"); #endifif(gpio_pmodoled_class) {class_destroy(gpio_pmodoled_class);}gpio_pmodoled_class = NULL;#ifdef CONFIG_PMODS_DEBUGprintk(KERN_INFO DRIVER_NAME " : Release Char Device Region.\n"); #endifunregister_chrdev_region(gpio_pmodoled_dev_id, MAX_PMODOLED_GPIO_DEV_NUM);gpio_pmodoled_dev_id = 0;}return 0; }當syscall調用remove函數時,會傳遞給函數一個PDEV描述塊的指針pdev。
首先定義一系列指針,初始化后,方便作為句柄使用。
將pdev->dev.of_node賦值給np,這是一個device_node型的指針,后續使用np作為句柄使用。
判斷device_node的data成員。
如果為NULL,則是非法地址。打印錯誤信息,然后直接return。
如果不為NULL,則是合法地址。繼續執行。
將np->data強制轉換成struct gpio_pmodoled_device型的指針,并賦值給gpio_pmodoled_dev,后續使用gpio_pmodoled_dev作為PMOD設備的句柄。
判斷PMOD設備的成員disp_buf的合法性。
如果不為NULL,合法地址,說明分配了kernelmem資源。釋放這個資源。
kfree()負責將kernelmem資源釋放。
判斷PMOD設備的成員pdev的合法性。
如果不為NULL,合法地址,說明分配了kernelzone資源。釋放這個資源。
platform_device_unregister()負責將PDEV描述塊資源釋放。
此時,PMOD的內核資源已經釋放完。
將device_node中關聯到該設備的指針釋放。設置為NULL。
np->data = NULL;
我們定義了靜態變量device_num,它記錄了系統中注冊的PMOD的數量。
修改device_num,記錄當前PMOD設備數量。
device_num–;
判斷device_num的狀態。
如果不為NULL,說明仍然有PMOD設備存在,那么SPIDRIVER要繼續使用。跳過,不執行關聯操作。
如果為NULL,說明系統中已經沒有PMOD設備存在,那么SPIDRIVER不需要繼續使用了。執行關聯操作。
注銷SPIDRIVER。
spi_unregister_driver()負責注銷SPIDRIVER。釋放內核資源和描述塊。
我們定義了一個靜態變量gpio_pmodoled_spi_driver,它是SPIDRIVER的描述塊。
注銷SPIDRIVER后,需要記錄SPIDRIVER當前的系統狀態。
修改spi_drv_registered,這是一個靜態變量。
spi_drv_registered = 0;
表示當前系統中沒有注冊SPIDRIVER。
我們定義了一個靜態變量gpio_pmodoled_class,它是struct class型的指針,用來指向一個CLASS描述塊。CLASS描述塊是系統資源。
判斷gpio_pmodoled_class的合法性。
如果不為NULL,那么說明系統中已經分配了CLASS描述塊。
釋放CLASS描述塊。
class_destroy()負責釋放內核中的CLASS資源。
由于已經釋放了CLASS資源,gpio_pmodoled_class指針便指向了無效結構體對象。
修改gpio_pmodoled_class,設置為NULL。
我們定義了靜態變量gpio_pmodoled_dev_id,用來記錄申請到的DEV_ID,
注銷DEV_ID對應的CDEV,釋放CDEV的內核資源。
unregister_chrdev_region()負責根據DEV_ID找到CDEV,并釋放CDEV的region資源。
由于gpio_pmodoled_dev_id對應的CDEV已經釋放,所以這個DEV_ID便是一個無效ID。
修改gpio_pmodoled_dev_id,設置為NULL。
至此,所有需要釋放的資源都釋放了。
直接return。
總結
以上是生活随笔為你收集整理的添加内核驱动模块(1)(mydriver.c+ Konfig+Makefile )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 下载视频网站中ts格式的视频
- 下一篇: 《刺杀骑士团长》读后感