先給自己打個廣告,本人的微信公衆号正式上線了,搜尋:張笑生的地盤,主要關注嵌入式軟體開發,股票基金定投,足球等等,希望大家多多關注,有問題可以直接留言給我,一定盡心盡力回答大家的問題
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIwczX0xiRGZkRGZ0Xy9GbvNGL2EzXlpXazxSPr1WYuVzVZ9Gcup1cWJzY1EjMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZwpmL5ADOxQzMzYTM3ADMxkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
本系列文章還是為了記錄在學習韋東山老師的嵌入式開發教程中的課程筆記,并整理一個比較詳細的課堂筆記,友善一起學習的同學們參考。
如果還沒有購買韋老師的教學視訊,或者不知道去哪裡購買的,我這裡給大家一個小程式連結
接下來開始今天的正文部分
**
一 why
**
在初級字元裝置驅動程式編寫時,我們的應用程式是通過open函數來實作調用底層驅動的,比如:
fd = open("/dev/buttons", O_RDWR);
read(fd, &key_val, 1);
在這個架構下面的字元驅動裝置架構為:
1)寫file_operations結構體的成員函數: .open()、.read()、.write()
2)在入口函數裡通過register_chrdev()建立驅動名,生成主裝置号,賦入file_operations結構體
3)在出口函數裡通過unregister_chrdev() 解除安裝驅動
思考一下,這樣做有什麼不好的地方呢?
若有多個不同的驅動程式時,應用程式就要打開多個不同的驅動裝置,由于是自己寫肯定會很清楚,如果給别人來使用時是不是很麻煩?
是以,我們使用現成的驅動,将我們上面寫的驅動程式融合到核心中,這個現成的驅動就是input子系統。
**
二 what
**
linux核心為了能夠處理各種不同類型的輸入裝置,比如觸摸屏、滑鼠、鍵盤等等,為驅動層程式提供統一的接口函數,為上層應用提供試圖統一的抽象層,這就是輸入子系統input system。
輸入子系統架構分析
a. input子系統組成及其架構圖
輸入子系統由輸入子系統核心層( Input Core ),驅動層和事件處理層(Event Handler)三部份組成
圖檔來源:https://blog.csdn.net/yetaibing1990/article/details/82753515
b. 核心層
根據上面的圖可知,顯然核心層是連接配接左右兩邊的device和driver的橋梁,源碼為
/drivers/input/input.c。我們閱讀一下它的入口函數input_init()
static const struct file_operations input_fops = {
.owner = THIS_MODULE,
.open = input_open_file,
.llseek = noop_llseek,
};
......
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);
(1)那麼應該怎麼去讀呢,input_open_file是如何實作呢?
input_open_file
handler = input_table[iminor(inode) >> 5];
new_fops = fops_get(handler->fops); // 假設 => evdev_fops(evdev.c中)
file->f_op = new_fops;
err = new_fops->open(inode, file);
app應用程式讀的時候,應用程式調用read函數,一直會調用到核心底層的file->f_op->read
read > ... > file->f_op->read
在上面的code中,input_table 數組由誰構造?
input_register_handler
注冊 input_handler
input_register_handler
// 放入數組
input_table[handler->minor >> 5] = handler;
// 放傳入連結表
list_add_tail(&handler->node, &input_handler_list);
// 對于每一個input_dev, 調用 input_attach_handler
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler); // 根據input_handler的 id_table 判斷能否支援這個 input_device
注冊輸入裝置
input_register_device
// 放傳入連結表中
list_add_tail(&dev->node, &input_dev_list);
// 對于每一個 input_handler 都調用 input_attach_handler 函數
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler); // 根據input_handler的 id_table 判斷能否支援這個 input_device
input_attach_handler
id = input_match_device(handler, dev);
error = handler->connect(handler, dev, id);
input_attach_handler
id = input_match_device(handler, dev);
error = handler->connect(handler, dev, id);
注冊 input_dev 和 input_handler 時, 會兩兩比較左邊的 input_dev 和右邊的 input_handler,根據 input_handler 的 id_table判斷這個 input_handler能夠支援這個 input_dev。如果能支援,則調用 input_handler的connect函數建立"連接配接"。
(2)怎麼建立連接配接?
- 配置設定一個 input_handle結構體
-
input_handle.dev = input_dev; //指向架構圖左邊的dev結構體
input_handle.handler = input_handler; //指向架構圖右邊的handler結構體
-
注冊
input_handler->h_list = &input_handle
input_dev->h_list = &input_handle
evdev_connect
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); 配置設定一個 input_handle
evdev->handle.dev = input_get_device(dev); //指向架構圖左邊的dev結構體
evdev->handle.name = dev_name(&evdev->dev);
evdev->handle.handler = handler; //指向架構圖右邊的handler結構體
evdev->handle.private = evdev;
(3)怎麼寫符合輸入子系統架構的驅動程式?
1. 配置設定一個 input_dev 結構體
2. 設定
3. 注冊
4. 硬體相關的代碼,比如中斷服務程式裡上報事件
**
三 how
**
根據上面的分析,顯然我們的驅動程式需要實作兩個子產品,dev驅動以及drv驅動,我們以點亮一個led為例,分别實作led_dev以及led_drv
a. 實作led_dev.c
(1)初始化和退出驅動,這個調用platform_device_register和platform_device_unregister即可
static int led_dev_init(void)
{
platform_device_register(&led_dev);
return 0;
}
static void led_dev_exit(void)
{
platform_device_unregister(&led_dev);
}
(2)初始化platform_device結構體
static struct platform_device led_dev = {
.name = "myled",
.id = -1,
.num_resources = ARRAY_SIZE(led_resource),
.resource = led_resource,
.dev = {
.release = led_release,
},
};
(3)初始化device資源,第0個表示led使用的gpio寄存器,第1個表示gpio引腳
static struct resource led_resource[] = {
[0] = {
.start = 0x56000050,
.end = 0x56000050 + 8 - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = 5,
.end = 5,
.flags = IORESOURCE_IRQ,
},
};
完整代碼如下:
/* 配置設定設定注冊一個platform_device */
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/timer.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/platform_device.h>
#include <linux/io.h>
static void led_release(struct device *dev)
{
return 0;
}
static struct resource led_resource[] = {
[0] = {
.start = 0x56000050,
.end = 0x56000050 + 8 - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = 5,
.end = 5,
.flags = IORESOURCE_IRQ,
},
};
static struct platform_device led_dev = {
.name = "myled",
.id = -1,
.num_resources = ARRAY_SIZE(led_resource),
.resource = led_resource,
.dev = {
.release = led_release,
},
};
static int led_dev_init(void)
{
platform_device_register(&led_dev);
return 0;
}
static void led_dev_exit(void)
{
platform_device_unregister(&led_dev);
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
b. 實作led_drv.c
(1) 初始化和退出驅動,這個調用platform_driver_register和platform_driver_unregister即可
static int led_drv_init(void)
{
platform_driver_register(&led_drv);
return 0;
}
static void led_drv_exit(void)
{
platform_driver_unregister(&led_drv);
}
(2) 初始化platform_driver
static struct platform_driver led_drv = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "myled",
.owner = THIS_MODULE,
}
};
(3)led_probe以及led_remove
static int led_probe(struct platform_device *pdev)
{
struct resource *res;
/* 根據platform的資源進行ioremap */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
//printk("res=0x%p res->start=0x%p\n", res, res->start);
if (res == NULL){
printk("get resource err!\n");
return -1;
}
gpio_con = ioremap(res->start, res->end - res->start + 1);
gpio_dat = gpio_con + 1;
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (res == NULL){
printk("get resource err!\n");
return -2;
}
//printk("res1=0x%p res1->start=0x%p\n", res, res->start);
pin = res->start;
/* 注冊字元裝置驅動 */
printk("led_probe, found led\n");
major = register_chrdev(0, "myled", &led_fops);
cls = class_create(THIS_MODULE, "myled");
device_create(cls, NULL, MKDEV(major, 0), NULL, "led");
return 0;
}
static int led_remove(struct platform_device *pdev)
{
/* 解除安裝字元裝置驅動 */
/* 根據platform的資源進行iounmap */
printk("led_remove, remove led\n");
device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
unregister_chrdev(major, "myled");
iounmap(gpio_con);
return 0;
}
(4)初始化file_operations
static struct file_operations led_fops = {
.owner = THIS_MODULE, /* 這是一個宏,推向編譯子產品時自動建立的__this_module變量 */
.open = led_open,
.write = led_write,
};
(5)led_open和led_write
static int led_open(struct inode *inode, struct file *file)
{
/* 配置為輸出引腳 */
*gpio_con &= ~(0x3<<(pin*2));
*gpio_con |= (0x1<<(pin*2));
return 0;
}
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val;
//printk("first_drv_write\n");
copy_from_user(&val, buf, count); // copy_to_user();
if (val == 1)
{
// 點燈
*gpio_dat &= ~(1<<pin);
}
else
{
// 滅燈
*gpio_dat |= (1<<pin);
}
return 0;
}
完整代碼
/* 配置設定設定注冊一個platform_driver */
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>
static int major;
static struct class *cls;
static volatile unsigned long *gpio_con;
static volatile unsigned long *gpio_dat;
static int pin;
static int led_open(struct inode *inode, struct file *file)
{
/* 配置為輸出引腳 */
*gpio_con &= ~(0x3<<(pin*2));
*gpio_con |= (0x1<<(pin*2));
return 0;
}
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
int val;
//printk("first_drv_write\n");
copy_from_user(&val, buf, count); // copy_to_user();
if (val == 1)
{
// 點燈
*gpio_dat &= ~(1<<pin);
}
else
{
// 滅燈
*gpio_dat |= (1<<pin);
}
return 0;
}
static struct file_operations led_fops = {
.owner = THIS_MODULE, /* 這是一個宏,推向編譯子產品時自動建立的__this_module變量 */
.open = led_open,
.write = led_write,
};
static int led_probe(struct platform_device *pdev)
{
struct resource *res;
/* 根據platform的資源進行ioremap */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
//printk("res=0x%p res->start=0x%p\n", res, res->start);
if (res == NULL){
printk("get resource err!\n");
return -1;
}
gpio_con = ioremap(res->start, res->end - res->start + 1);
gpio_dat = gpio_con + 1;
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (res == NULL){
printk("get resource err!\n");
return -2;
}
//printk("res1=0x%p res1->start=0x%p\n", res, res->start);
pin = res->start;
/* 注冊字元裝置驅動 */
printk("led_probe, found led\n");
major = register_chrdev(0, "myled", &led_fops);
cls = class_create(THIS_MODULE, "myled");
device_create(cls, NULL, MKDEV(major, 0), NULL, "led");
return 0;
}
static int led_remove(struct platform_device *pdev)
{
/* 解除安裝字元裝置驅動 */
/* 根據platform的資源進行iounmap */
printk("led_remove, remove led\n");
device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
unregister_chrdev(major, "myled");
iounmap(gpio_con);
return 0;
}
static struct platform_driver led_drv = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "myled",
.owner = THIS_MODULE,
}
};
static int led_drv_init(void)
{
platform_driver_register(&led_drv);
return 0;
}
static void led_drv_exit(void)
{
platform_driver_unregister(&led_drv);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
c. 測試程式,驗證上面的驅動架構,完整代碼如下
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
/* led_test on
* led_test off
*/
int main(int argc, char **argv)
{
int fd;
int val = 1;
fd = open("/dev/led", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
return 0;
}
if (argc != 2)
{
printf("Usage :\n");
printf("%s <on|off>\n", argv[0]);
return 0;
}
if (strcmp(argv[1], "on") == 0)
{
val = 1;
}
else
{
val = 0;
}
write(fd, &val, 4);
return 0;
}
**
四 驗證測試
**
a. 編譯并上傳至nfs
make
arm-linux-gcc -o led_test led_test.c
cp *.ko /work/nfs_root/fs_mini_mdev_new
b. 加載驅動
insmod led_dev.ko
insmod led_drv.ko
c. 運作測試,此時gpio4對應的led燈會被點亮或者熄滅
chmod +x led_test
./led_test on
./led_test off