添加内核驱动模块(5)(mydriver.c+ Konfig+Makefile )
首先定義一個(gè)fops。
struct file_operations gpio_pmodoled_cdev_fops = {.owner = THIS_MODULE,.write = gpio_pmodoled_write,.read = gpio_pmodoled_read,.open = gpio_pmodoled_open,.release = gpio_pmodoled_close, };初始化各個(gè)成員。
賦值了owner,指向標(biāo)準(zhǔn)宏THIS_MODULE。
賦值了open,指向函數(shù)gpio_pmodoled_open。
賦值了release,指向函數(shù)gpio_pmodoled_release.
賦值了read,指向函數(shù)gpio_pmodoled_read。
賦值了write,指向函數(shù)gpio_pmodoled_write。
首先編寫open函數(shù)。
static int gpio_pmodoled_open(struct inode *inode, struct file *fp) {struct gpio_pmodoled_device *dev;dev = container_of(inode->i_cdev, struct gpio_pmodoled_device, cdev);fp->private_data = dev;return 0; }當(dāng)syscall調(diào)用open函數(shù)時(shí),會傳遞給函數(shù)一個(gè)inode指針,一個(gè)file指針。
container_of()用來找到一個(gè)數(shù)據(jù)結(jié)構(gòu)的容器。它根據(jù)兩個(gè)數(shù)據(jù)結(jié)構(gòu)之間的內(nèi)存分布關(guān)系,計(jì)算偏移量,從而獲得容器指針。
我們利用這個(gè)inode,找到它的i_cdev成員,定位到了PMOD的子成員cdev,通過偏移返回PMOD的指針。
將file的private_data填充為剛申請的DDB的指針。
然后編寫release函數(shù)。
static int gpio_pmodoled_release(struct inode *inode, struct file *fp) {return 0; }當(dāng)syscall調(diào)用release函數(shù)時(shí),會傳遞給函數(shù)一個(gè)inode指針,一個(gè)file指針。
我們可以在其中釋放一些open函數(shù)里申請的系統(tǒng)資源,如果沒有申請,就不用釋放。
之前的open里,我們并沒有申請系統(tǒng)資源。僅僅只是把指針進(jìn)行了重定向。
所以這里什么也不用做。
然后編寫read函數(shù)。
static ssize_t gpio_pmodoled_read(struct file *fp, char __user *buffer, size_t length, loff_t *offset) {ssize_t retval = 0;struct gpio_pmodoled_device *dev;unsigned int minor_id;int cnt;dev = fp->private_data;minor_id = MINOR(dev->dev_id);if(mutex_lock_interruptible(&dev->mutex)) {retval = -ERESTARTSYS;goto read_lock_err;}if (buffer == NULL) {dev_err(&dev->spi->dev, "OLED_read: ERROR: invalid buffer ""address: 0x%08X\n", (unsigned int)buffer);retval = -EINVAL;goto quit_read;}if (length > DISPLAY_BUF_SZ)cnt = DISPLAY_BUF_SZ;elsecnt = length;retval = copy_to_user((void *)buffer, dev->disp_buf, cnt);if (!retval)retval = cnt; /* copy success, return amount in buffer */quit_read:mutex_unlock(&dev->mutex); read_lock_err:return(retval); }當(dāng)syscall調(diào)用read函數(shù)時(shí),
會傳遞給函數(shù)一個(gè)file指針fp,指向文件描述塊。
一個(gè)char指針buffer。指向緩沖區(qū)。
一個(gè)length,對緩沖區(qū)限界。
一個(gè)loff指針offset,記錄當(dāng)前的偏移量。
首先利用fp獲得dev指針,這是PMOD的DDB的指針。
然后利用dev獲得minor_id。這是次設(shè)備號。注意,主設(shè)備號major用來區(qū)分驅(qū)動ID。
申請互斥鎖,mutex_lock_interruptible()用來申請mutex。如果申請失敗,轉(zhuǎn)入錯誤處理流程read_lock_err。錯誤處理流程,采用堆棧式寫法。發(fā)生錯誤的位置越靠前,goto的位置就越靠后。
成功進(jìn)入互斥區(qū)后,
判斷buffer的合法性。
如果buffer非法,等于NULL,那么打印出錯信息。dev_err()打印錯誤信息。將格式化字符串輸出到設(shè)備的stderr。然后轉(zhuǎn)入錯誤處理流程quit_read。由于quit_read要回溯之前已經(jīng)做過的操作,所以它在read_lock_err的上方。這是堆棧式寫法的標(biāo)準(zhǔn)寫法。
buffer合法后,
判斷l(xiāng)ength的合法性。
如果length越界,那么修正length,限界到最大值。
length合法后,
從本設(shè)備的緩沖區(qū)拷貝數(shù)據(jù),傳送到用戶數(shù)據(jù)緩沖區(qū),用戶數(shù)據(jù)緩沖區(qū),位于userspace。所以用copy_to_user()。
拷貝操作完成后,
判斷返回值的合法性。
如果成功,則將cnt作為retval存下,準(zhǔn)備作為返回值使用。
執(zhí)行到此,相當(dāng)于要回溯所有的退出處理流程。而退出處理流程代碼都寫在函數(shù)體的最后面,所以默認(rèn)會執(zhí)行這些代碼。
這里用到了一個(gè)宏,我們定義它。
#define DISPLAY_BUF_SZ 512 /* 32 x 128 bit monochrome == 512 bytes */然后編寫write函數(shù)。
static ssize_t gpio_pmodoled_write(struct file *fp, const char __user *buffer, size_t length, loff_t *offset) {ssize_t retval = 0;struct gpio_pmodoled_device *dev;unsigned int minor_id;int cnt;int status;dev = fp->private_data;minor_id = MINOR(dev->dev_id);if(mutex_lock_interruptible(&dev->mutex)) {retval = -ERESTARTSYS;goto write_lock_err;}if (buffer == NULL) {dev_err(&dev->spi->dev, "oled_write: ERROR: invalid buffer address: 0x%08x\n",(unsigned int) buffer);retval = -EINVAL;goto quit_write;}if(length > DISPLAY_BUF_SZ) {cnt = DISPLAY_BUF_SZ;}else {cnt = length;}if(copy_from_user(dev->disp_buf, buffer, cnt)) {dev_err(&dev->spi->dev, "oled_write: copy_from_user failed\n");retval = -EFAULT;goto quit_write;}else {retval = cnt;}status = screen_buf_to_display(dev->disp_buf, dev);if(status) {dev_err(&dev->spi->dev, "oled_write: Error sending string to display\n");retval = -EFAULT;goto quit_write;}quit_write:mutex_unlock(&dev->mutex); write_lock_err:return retval; }當(dāng)syscall調(diào)用write函數(shù)時(shí),
會傳遞給函數(shù)一個(gè)file指針fp,指向文件描述塊。
一個(gè)char指針buffer。指向緩沖區(qū)。
一個(gè)length,對緩沖區(qū)限界。
一個(gè)loff指針offset,記錄當(dāng)前的偏移量。
首先利用fp獲得dev指針,這是PMOD的DDB的指針。
然后利用dev獲得minor_id。這是次設(shè)備號。注意,主設(shè)備號major用來區(qū)分驅(qū)動ID。
申請互斥鎖,mutex_lock_interruptible()用來申請mutex。如果申請失敗,轉(zhuǎn)入錯誤處理流程write_lock_err。錯誤處理流程,采用堆棧式寫法。
成功進(jìn)入互斥區(qū)后,
判斷buffer的合法性。
如果buffer非法,等于NULL,那么打印出錯信息。dev_err()打印錯誤信息。將格式化字符串輸出到設(shè)備的stderr。然后轉(zhuǎn)入退出處理?xiàng)uit_write。由于quit_write要回溯之前已經(jīng)做過的操作,所以它在read_lock_err的上方。這是堆棧式寫法的標(biāo)準(zhǔn)寫法。
buffer合法后,
判斷l(xiāng)ength的合法性。
如果length越界,那么修正length,限界到最大值。
length合法后,
從本設(shè)備的緩沖區(qū)拷貝數(shù)據(jù),傳送到用戶數(shù)據(jù)緩沖區(qū),用戶數(shù)據(jù)緩沖區(qū),位于userspace。所以用copy_from_user()。
拷貝操作完成后,
判斷返回值的合法性。
如果報(bào)錯,那么打印錯誤信息,然后轉(zhuǎn)入退出處理?xiàng)uit_write。
如果成功,那么將cnt賦值給retval,設(shè)置當(dāng)前步驟下的retval。
拷貝成功后,
將緩沖區(qū)內(nèi)容送到顯示屏上顯示。screen_buf_to_display()用于顯示。
完成后,返回狀態(tài)值。
判斷狀態(tài),
如果失敗,那么打印錯誤信息,并轉(zhuǎn)入退出處理?xiàng)uit_write。
如果成功,那么正常退出。執(zhí)行全部的退出處理?xiàng)4a。
這里我們用到了一個(gè)子函數(shù)screen_buf_to_display(),用來送顯示屏。
現(xiàn)在我們來編寫這個(gè)函數(shù)。
caller傳遞給screen_buf_to_display()函數(shù),
一個(gè)char指針screen_buf,指示緩沖區(qū)的基地址。
一個(gè)PMOD的DDB指針dev,指向PMOD的DDB。
策略上,準(zhǔn)備采用循環(huán)方式。所以設(shè)置了循環(huán)控制變量pg。逐個(gè)pg進(jìn)行操作。
定義了宏,限定pg的最大值。
#define OLED_MAX_PG_CNT 4 /* number of display pages in OLED controller */
定義了宏,設(shè)置pg的基地址。
#define OLED_SET_PG_ADDR 0x22
配置好wr_buf,這是給SPI操作使用的緩沖區(qū)。
利用dev中保存的GPIO引腳號,直接操作GPIO。
gpio_set_value()是GPIOLIB中的函數(shù)。它將一個(gè)布爾值,寫入系統(tǒng)中已經(jīng)注冊了編號的GPIO中。
這里我們定義了宏,設(shè)置控制字。
#define OLED_CONTROLLER_CMD 0
利用本設(shè)備中的SPI成員,完成SPI的寫操作時(shí)序。
spi_write()是SPILIB的函數(shù)。它的參數(shù)包括,一個(gè)SPI的DDB描述塊,一個(gè)緩沖區(qū)的指針與長度限界。將緩沖區(qū)的數(shù)據(jù),逐個(gè)發(fā)送到SPI總線上,實(shí)現(xiàn)寫時(shí)序。最后返回狀態(tài)值。
操作完成后,
判斷status。
如果非法,那么打印出錯信息,跳出循環(huán),進(jìn)入退出處理?xiàng)!?br /> 如果合法,那么繼續(xù)執(zhí)行。
利用dev中保存的GPIO引腳號,直接操作GPIO。
gpio_set_value()是GPIOLIB中的函數(shù)。它將一個(gè)布爾值,寫入系統(tǒng)中已經(jīng)注冊了編號的GPIO中。
這里我們定義了宏,設(shè)置控制字。
#define OLED_CONTROLLER_DATA 1
利用本設(shè)備中的SPI成員,完成SPI的寫操作時(shí)序。
spi_write()是SPILIB的函數(shù)。它的參數(shù)包括,一個(gè)SPI的DDB描述塊,一個(gè)緩沖區(qū)的指針與長度限界。將緩沖區(qū)的數(shù)據(jù),逐個(gè)發(fā)送到SPI總線上,實(shí)現(xiàn)寫時(shí)序。最后返回狀態(tài)值。
定義了宏,設(shè)置了數(shù)據(jù)限界。
#define OLED_CONTROLLER_PG_SZ 128
一次寫入一個(gè)PAGE。
操作完成后,
判斷status。
如果非法,那么打印出錯信息,跳出循環(huán),進(jìn)入退出處理?xiàng)!?br /> 如果合法,那么繼續(xù)執(zhí)行。
到此,循環(huán)體的一次執(zhí)行全部結(jié)束。
跳轉(zhuǎn),進(jìn)入下一次循環(huán)。
直至全部完成。
循環(huán)結(jié)束后,進(jìn)入最終退出處理?xiàng)!2⒎祷亍?/p>
至此,fops中需要的四個(gè)函數(shù)全部完成。
總結(jié)
以上是生活随笔為你收集整理的添加内核驱动模块(5)(mydriver.c+ Konfig+Makefile )的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 编程电子书汇总
- 下一篇: 程序员应该阅读的一些书籍