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