目錄
實驗程序編寫
修改設備樹文件
定時器驅動程序
timer.c
測試 timerApp.c
Makefile 文件
運行測試
實驗程序編寫
本講實驗,我們使用正點原子I.MX6U-ALPHA 開發板,通過linux內核定時器周期性的點亮和熄滅開發板上的 LED 燈, LED 燈的閃爍周期由內核定時器來設置,測試應用程序可以控制內核定時器周期。
修改設備樹文件
和之前文章一致,GPIO子系統驅動LED,主要是以下幾點:
- 添加 pinctrl 節點
- 添加 LED 設備節點
- 檢查 PIN 是否被其他外設使用
定時器驅動程序
timer.c
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define TIMER_CNT 1 /* 設備號個數 */
#define TIMER_NAME "timer" /* 名字 */
#define CLOSE_CMD (_IO(0XEF, 0x1)) /* 關閉定時器 */
#define OPEN_CMD (_IO(0XEF, 0x2)) /* 打開定時器 */
#define SETPERIOD_CMD (_IO(0XEF, 0x3)) /* 設置定時器周期命令 */
#define LEDON 1 /* 開燈 */
#define LEDOFF 0 /* 關燈 *//* timer設備結構體 */
struct timer_dev{dev_t devid; /* 設備號 */struct cdev cdev; /* cdev */struct class *class; /* 類 */struct device *device; /* 設備 */int major; /* 主設備號 */int minor; /* 次設備號 */struct device_node *nd; /* 設備節點 */int led_gpio; /* key所使用的GPIO編號 */int timeperiod; /* 定時周期,單位為ms */struct timer_list timer;/* 定義一個定時器*/spinlock_t lock; /* 定義自旋鎖 */
};struct timer_dev timerdev; /* timer設備 *//** @description : 初始化LED燈IO,open函數打開驅動的時候* 初始化LED燈所使用的GPIO引腳。* @param : 無* @return : 無*/
static int led_init(void)
{int ret = 0;timerdev.nd = of_find_node_by_path("/gpioled");if (timerdev.nd== NULL) {return -EINVAL;}timerdev.led_gpio = of_get_named_gpio(timerdev.nd ,"led-gpio", 0);if (timerdev.led_gpio < 0) {printk("can't get led\r\n");return -EINVAL;}/* 初始化led所使用的IO */gpio_request(timerdev.led_gpio, "led"); /* 請求IO */ret = gpio_direction_output(timerdev.led_gpio, 1);if(ret < 0) {printk("can't set gpio!\r\n");}return 0;
}/** @description : 打開設備* @param - inode : 傳遞給驅動的inode* @param - filp : 設備文件,file結構體有個叫做private_data的成員變量* 一般在open的時候將private_data指向設備結構體。* @return : 0 成功;其他 失敗*/
static int timer_open(struct inode *inode, struct file *filp)
{int ret = 0;filp->private_data = &timerdev; /* 設置私有數據 */timerdev.timeperiod = 1000; /* 默認周期為1s */ret = led_init(); /* 初始化LED IO */if (ret < 0) {return ret;}return 0;
}/** @description : ioctl函數,* @param - filp : 要打開的設備文件(文件描述符)* @param - cmd : 應用程序發送過來的命令* @param - arg : 參數* @return : 0 成功;其他 失敗*/
static long timer_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{struct timer_dev *dev = (struct timer_dev *)filp->private_data;int timerperiod;unsigned long flags;switch (cmd) {case CLOSE_CMD: /* 關閉定時器 */del_timer_sync(&dev->timer);break;case OPEN_CMD: /* 打開定時器 */spin_lock_irqsave(&dev->lock, flags);timerperiod = dev->timeperiod;spin_unlock_irqrestore(&dev->lock, flags);mod_timer(&dev->timer, jiffies + msecs_to_jiffies(timerperiod));break;case SETPERIOD_CMD: /* 設置定時器周期 */spin_lock_irqsave(&dev->lock, flags);dev->timeperiod = arg;spin_unlock_irqrestore(&dev->lock, flags);mod_timer(&dev->timer, jiffies + msecs_to_jiffies(arg));break;default:break;}return 0;
}/* 設備操作函數 */
static struct file_operations timer_fops = {.owner = THIS_MODULE,.open = timer_open,.unlocked_ioctl = timer_unlocked_ioctl,
};/* 定時器回調函數 */
void timer_function(unsigned long arg)
{struct timer_dev *dev = (struct timer_dev *)arg;static int sta = 1;int timerperiod;unsigned long flags;sta = !sta; /* 每次都取反,實現LED燈反轉 */gpio_set_value(dev->led_gpio, sta);/* 重啟定時器 */spin_lock_irqsave(&dev->lock, flags);timerperiod = dev->timeperiod;spin_unlock_irqrestore(&dev->lock, flags);mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod)); }/** @description : 驅動入口函數* @param : 無* @return : 無*/
static int __init timer_init(void)
{/* 初始化自旋鎖 */spin_lock_init(&timerdev.lock);/* 注冊字符設備驅動 *//* 1、創建設備號 */if (timerdev.major) { /* 定義了設備號 */timerdev.devid = MKDEV(timerdev.major, 0);register_chrdev_region(timerdev.devid, TIMER_CNT, TIMER_NAME);} else { /* 沒有定義設備號 */alloc_chrdev_region(&timerdev.devid, 0, TIMER_CNT, TIMER_NAME); /* 申請設備號 */timerdev.major = MAJOR(timerdev.devid); /* 獲取分配號的主設備號 */timerdev.minor = MINOR(timerdev.devid); /* 獲取分配號的次設備號 */}/* 2、初始化cdev */timerdev.cdev.owner = THIS_MODULE;cdev_init(&timerdev.cdev, &timer_fops);/* 3、添加一個cdev */cdev_add(&timerdev.cdev, timerdev.devid, TIMER_CNT);/* 4、創建類 */timerdev.class = class_create(THIS_MODULE, TIMER_NAME);if (IS_ERR(timerdev.class)) {return PTR_ERR(timerdev.class);}/* 5、創建設備 */timerdev.device = device_create(timerdev.class, NULL, timerdev.devid, NULL, TIMER_NAME);if (IS_ERR(timerdev.device)) {return PTR_ERR(timerdev.device);}/* 6、初始化timer,設置定時器處理函數,還未設置周期,所有不會激活定時器 */init_timer(&timerdev.timer);timerdev.timer.function = timer_function;timerdev.timer.data = (unsigned long)&timerdev;return 0;
}/** @description : 驅動出口函數* @param : 無* @return : 無*/
static void __exit timer_exit(void)
{gpio_set_value(timerdev.led_gpio, 1); /* 卸載驅動的時候關閉LED */del_timer_sync(&timerdev.timer); /* 刪除timer */
#if 0del_timer(&timerdev.tiemr);
#endif/* 注銷字符設備驅動 */gpio_free(timerdev.led_gpio); cdev_del(&timerdev.cdev);/* 刪除cdev */unregister_chrdev_region(timerdev.devid, TIMER_CNT); /* 注銷設備號 */device_destroy(timerdev.class, timerdev.devid);class_destroy(timerdev.class);
}module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("huax");
關鍵代碼分析如下:
定時器設備結構體timer_dev,定義了一個定時器成員變量 timer:
/* timer設備結構體 */
struct timer_dev{dev_t devid; /* 設備號 */struct cdev cdev; /* cdev */struct class *class; /* 類 */struct device *device; /* 設備 */int major; /* 主設備號 */int minor; /* 次設備號 */struct device_node *nd; /* 設備節點 */int led_gpio; /* key所使用的GPIO編號 */int timeperiod; /* 定時周期,單位為ms */struct timer_list timer;/* 定義一個定時器*/spinlock_t lock; /* 定義自旋鎖 */
};
函數 timer_open,對應應用程序的 open 函數,應用程序調用 open 函數打開/dev/timer 驅動文件的時候此函數就會執行。
static int timer_open(struct inode *inode, struct file *filp)
{int ret = 0;filp->private_data = &timerdev; /* 設置私有數據 */timerdev.timeperiod = 1000; /* 默認周期為1s */ret = led_init(); /* 初始化LED IO */if (ret < 0) {return ret;}return 0;
}
此函數設置文件私有數據為 timerdev,并且初始化定時周期默認為 1 秒,最后調用 led_init 函數初始化 LED 所使用的 IO。
函數 timer_unlocked_ioctl,對應應用程序的 ioctl 函數,應用程序調用 ioctl函數向驅動發送控制信息,此函數響應并執行。
static long timer_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{struct timer_dev *dev = (struct timer_dev *)filp->private_data;int timerperiod;unsigned long flags;switch (cmd) {case CLOSE_CMD: /* 關閉定時器 */del_timer_sync(&dev->timer);break;case OPEN_CMD: /* 打開定時器 */spin_lock_irqsave(&dev->lock, flags);timerperiod = dev->timeperiod;spin_unlock_irqrestore(&dev->lock, flags);mod_timer(&dev->timer, jiffies + msecs_to_jiffies(timerperiod));break;case SETPERIOD_CMD: /* 設置定時器周期 */spin_lock_irqsave(&dev->lock, flags);dev->timeperiod = arg;spin_unlock_irqrestore(&dev->lock, flags);mod_timer(&dev->timer, jiffies + msecs_to_jiffies(arg));break;default:break;}return 0;
}
此函數有三個參數: filp, cmd 和 arg,其中 :
- filp是對應的設備文件,
- cmd 是應用程序發送過來的命令信息,
- arg 是應用程序發送過來的參數,
在本講定時器實驗里, arg 參數表示定時周期。
cmd?有三種命令:
- CLOSE_CMD: 關閉定時器命令, 調用 del_timer_sync 函數關閉定時器。
- OPEN_CMD:打開定時器命令,調用 mod_timer 函數打開定時器,定時周期為 timerdev 的timeperiod 成員變量,定時周期默認是 1 秒。
- SETPERIOD_CMD:設置定時器周期命令,參數 arg 就是新的定時周期,設置 timerdev 的timeperiod 成員變量為 arg 所表示定時周期指。并且使用 mod_timer 重新打開定時器,使定時器以新的周期運行。
函數 timer_function,定時器服務函數,此函有一個參數 arg, 對應timerdev 的地址,這樣通過 arg 參數就可以訪問到設備結構體。
void timer_function(unsigned long arg)
{struct timer_dev *dev = (struct timer_dev *)arg;static int sta = 1;int timerperiod;unsigned long flags;sta = !sta; /* 每次都取反,實現LED燈反轉 */gpio_set_value(dev->led_gpio, sta);/* 重啟定時器 */spin_lock_irqsave(&dev->lock, flags);timerperiod = dev->timeperiod;spin_unlock_irqrestore(&dev->lock, flags);mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->timeperiod)); }
當定時周期到了以后此函數就會被調用。在此函數中將 LED 燈的狀態取反,實現 LED 燈閃爍的效果。
函數 timer_init,驅動入口函數里,初始化定時器,設置定時器的定時處理函數為 timer_function,另外設置要傳遞給 timer_function 函數的參數為 timerdev的地址。
/* 6、初始化timer,設置定時器處理函數,還未設置周期,所有不會激活定時器 */init_timer(&timerdev.timer);timerdev.timer.function = timer_function;timerdev.timer.data = (unsigned long)&timerdev;
函數 timer_exit里,調用 del_timer_sync 函數刪除定時器。
del_timer_sync(&timerdev.timer); /* 刪除timer */
#if 0del_timer(&timerdev.tiemr);
#endif
測試 timerApp.c
測試 APP 我們要實現的內容如下:
- 運行 APP 以后提示我們輸入要測試的命令,輸入 1 表示關閉定時器、輸入 2 表示打開定時器,輸入 3 設置定時器周期。
- 如果要設置定時器周期的話,需要讓用戶輸入要設置的周期值,單位為毫秒。
timerApp.c文件代碼如下:
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"/* 命令值 */
#define CLOSE_CMD (_IO(0XEF, 0x1)) /* 關閉定時器 */
#define OPEN_CMD (_IO(0XEF, 0x2)) /* 打開定時器 */
#define SETPERIOD_CMD (_IO(0XEF, 0x3)) /* 設置定時器周期命令 *//** @description : main主程序* @param - argc : argv數組元素個數* @param - argv : 具體參數* @return : 0 成功;其他 失敗*/
int main(int argc, char *argv[])
{int fd, ret;char *filename;unsigned int cmd;unsigned int arg;unsigned char str[100];if (argc != 2) {printf("Error Usage!\r\n");return -1;}filename = argv[1];fd = open(filename, O_RDWR);if (fd < 0) {printf("Can't open file %s\r\n", filename);return -1;}while (1) {printf("Input CMD:");ret = scanf("%d", &cmd);if (ret != 1) { /* 參數輸入錯誤 */gets(str); /* 防止卡死 */}if(cmd == 1) /* 關閉LED燈 */cmd = CLOSE_CMD;else if(cmd == 2) /* 打開LED燈 */cmd = OPEN_CMD;else if(cmd == 3) {cmd = SETPERIOD_CMD; /* 設置周期值 */printf("Input Timer Period:");ret = scanf("%d", &arg);if (ret != 1) { /* 參數輸入錯誤 */gets(str); /* 防止卡死 */}}ioctl(fd, cmd, arg); /* 控制定時器的打開和關閉 */ }close(fd);
}
while(1)循環,讓用戶輸入要測試的命令,然后通過 ioctl 函數發送給驅動程序。
如果是設置定時器周期命令 SETPERIOD_CMD,那么 ioctl 函數的 arg 參數就是用戶輸入的周期值。
Makefile 文件
makefile文件只需要修改?obj-m 變量的值,改為timer.o
KERNELDIR := /home/huax/linux/linux_test/linux-imx-rel_imx_4.1.15_2.1.0_gaCURRENT_PATH := $(shell pwd)
obj-m := timer.obuild: kernel_modules
kernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
運行測試
編譯代碼:
make -j32 //編譯makefile文件
arm-linux-gnueabihf-gcc timerApp.c -o timerApp //編譯測試程序
編譯成功以后,就會生成一個名為“timer.ko”的驅動模塊文件,和timerApp 這個應用程序。
將編譯出來的 timer.ko 和 timerApp 這兩個文件拷貝到 rootfs/lib/modules/4.1.15 目錄中,重啟開發板。
進入到目錄 lib/modules/4.1.15 中,輸入如下命令加載 timer.ko 驅動模塊:
depmod //第一次加載驅動的時候需要運行此命令
modprobe timer.ko //加載驅動
驅動加載成功以后,輸入命令來測試:
/timerApp /dev/timer
打印如下:
提示我們輸入cmd指令。
輸入“2”,打開定時器,此時 LED 燈就會以默認的 1 秒周期開始閃爍。
再輸入“3”來設置定時周期,根據提示輸入要設置的周期值:
輸入“500”,表示設置定時器周期值為 500ms,設置好以后 LED 燈就會以 500ms 為間隔,開始閃爍。
最后可以通過輸入“1”來關閉定時器。
卸載驅動的話輸入如下命令:
rmmod timer.ko