需求整理
在Linux設備樹中新增leds節點,其有3個gpio屬性,分別表示PE10對應led1,PF10對應led2,PE8對應led3,設備樹鍵值對如下:
?? ?leds {
?? ??? ?led1-gpio = <&gpioe 10 0>;
?? ??? ?led2-gpio = <&gpiof 10 0>;
?? ??? ?led3-gpio = <&gpioe 8 0>;
?? ?};
內核驅動實現對燈控模塊的初始化函數、模塊退出函數、燈控模塊各回調函數(open/release/unlocked_ioctl/read/write)。
應用程序實現對燈控模塊的控制,通過ioctl函數控制led亮滅。
驅動開發邏輯分析
1.驅動初始化
注冊字符設備 --> register_chrdev
申請一個struct class結構體,保存當前設備類的信息 -->?class_create
申請一個struct device結構體,保存當前設備節點的信息 -->?device_create
通過名稱查找設備節點 -->?of_find_node_by_name
2.初始化GPIO
獲取GPIO編號 -->?of_get_named_gpio
請求GPIO -->?gpio_request
設置GPIO方向為輸出 -->?gpio_direction_output
3.ioctl回調函數
獲取應用程序發送的值 -->?copy_from_user
處理應用程序發送的功能碼 --> 回調函數中的第二個參數的值(一般有應用程序通過ioctl命令發送)
4.驅動退出
釋放GPIO -->?gpio_free
注銷字符設備文件 -->?device_destroy
注銷字符設備類 -->?class_destroy
注銷字符設備 -->?unregister_chrdev
5.指定模塊
指定模塊初始化函數 -->?module_init
指定模塊注銷函數 -->?module_exit
應用程序開發邏輯分析
1.打開字符設備文件 --> open
2.發送功能碼和值給驅動 --> ioctl
3.功能碼 --> _IO() / _IOW / _IOR / _IOWR ...
詳細代碼
驅動程序 --> leds.c
#include <linux/init.h> // 包含內核初始化相關的頭文件
#include <linux/module.h> // 包含內核模塊相關的頭文件
#include <linux/of.h> // 包含設備樹操作相關的頭文件
#include <linux/gpio.h> // 包含 GPIO 操作相關的頭文件
#include <linux/of_gpio.h> // 包含設備樹 GPIO 相關的頭文件
#include <linux/fs.h> // 包含文件操作相關的頭文件
#include <linux/uaccess.h> // 包含用戶空間訪問內核空間相關的頭文件
#include <linux/device.h> // 包含設備相關的頭文件
#include "leds.h" // 包含自定義頭文件/* 設備樹節點定義leds {led1-gpio = <&gpioe 10 0>;led2-gpio = <&gpiof 10 0>;led3-gpio = <&gpioe 8 0>;};
*/
static struct class *led_class;
static struct device *led_device;
static struct device_node *leds_node; // 定義設備節點指針
static char kernel_buf[100]; // 定義緩沖區
int led1_id,led2_id,led3_id; // 定義 GPIO 編號
int led_major; // 定義主設備號static int led_open(struct inode *inode, struct file *file);
static int led_close(struct inode *inode, struct file *file);
static int led_read(struct file *file, char __user *buf, size_t count, loff_t *ppos);
static int led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos);
static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg);struct file_operations fops = {.open = led_open,.release = led_close,.unlocked_ioctl = led_ioctl,.read = led_read,.write = led_write,
};static int led_open(struct inode *inode, struct file *file)
{printk("led_open\n");return 0;
}static int led_close(struct inode *inode, struct file *file)
{printk("led_close\n");return 0;
}static int led_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{uint32_t n = copy_to_user(buf, kernel_buf, count);if(n){printk("copy_to_user failed\n");return -EFAULT;}printk("led_read\n");return 0;
}static int led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{uint32_t n = copy_from_user(kernel_buf, buf, count);if(n){printk("copy_from_user failed\n");return -EFAULT;}printk("led_write\n");return 0;
}static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{//獲取arg的值unsigned int value;if(copy_from_user(&value, (unsigned int *)arg, sizeof(unsigned int))){printk("copy_from_user failed\n");return -EFAULT;}switch(cmd){case LED_ON:switch(value){case 1:gpio_set_value(led1_id, 1);break;case 2:gpio_set_value(led2_id, 1);break;case 3:gpio_set_value(led3_id, 1);break;default: printk("cmd error\n");return -EINVAL;}break;case LED_OFF:switch(value){case 1:gpio_set_value(led1_id, 0);break;case 2:gpio_set_value(led2_id, 0);break;case 3:gpio_set_value(led3_id, 0);break;default: printk("cmd error\n");return -EINVAL;}break;default:printk("cmd error\n");return -EINVAL;}printk("led_ioctl\n");return 0;
}//通過設備樹的屬性名查找gpio,并初始化gpio
static int mygpio_init(struct device_node *np ,const char *name)
{int id;printk("name=%s\n", name); // 打印屬性名id = of_get_named_gpio(np, name, 0); // 獲取 GPIO 編號if (id < 0) // 如果獲取 GPIO 編號失敗{printk("get gpio number failed\n"); // 打印獲取 GPIO 編號失敗的消息return -ENODEV; // 返回錯誤碼}printk("get gpio number success\n"); // 打印獲取 GPIO 編號成功的消息if(gpio_request(id, NULL)) // 請求 GPIO{printk("request gpio failed\n"); // 打印請求 GPIO 失敗的消息return -ENODEV; // 返回錯誤碼} printk("request gpio success\n"); // 打印請求 GPIO 成功的消息if(gpio_direction_output(id, 0)) // 設置 GPIO 方向為輸出{printk("set gpio direction failed\n"); // 打印設置 GPIO 方向失敗的消息return -ENODEV; // 返回錯誤碼}printk("set gpio direction success\n"); // 打印設置 GPIO 方向成功的消息return id; // 返回 GPIO 編號
}static int __init leds_init(void) // 模塊初始化函數
{//字符設備注冊led_major = register_chrdev(0, "leds_control", &fops);if(led_major < 0){printk("register_chrdev failed\n");return -ENODEV;}printk("register_chrdev success:led_major = %d\n", led_major);//申請一個struct class結構體,保存當前設備類的信息led_class = class_create(THIS_MODULE, "leds_control");if(IS_ERR(led_class)){printk("class_create failed\n");unregister_chrdev(led_major, "leds_control"); // 注銷字符設備return -ENODEV;}printk("class_create success\n");//申請一個struct device結構體,保存當前設備節點的信息led_device = device_create(led_class, NULL, MKDEV(led_major, 0), NULL, "leds_control");if(IS_ERR(led_device)){printk("device_create failed\n");class_destroy(led_class); // 注銷類unregister_chrdev(led_major, "leds_control"); // 注銷字符設備return -ENODEV;}printk("device_create success\n");leds_node = of_find_node_by_name(NULL, "leds"); // 通過名稱查找設備節點//leds_node = of_find_compatible_node(NULL, NULL, "sjh,mynode"); // 通過兼容字符串查找設備節點if (!leds_node) // 如果未找到設備節點{printk("mynode node not found\n"); // 打印未找到節點的消息device_destroy(led_class, MKDEV(led_major, 0)); // 注銷設備class_destroy(led_class); // 注銷類unregister_chrdev(led_major, "leds_control"); // 注銷字符設備return -ENODEV; // 返回錯誤碼}printk("mynode node found\n"); // 打印找到節點的消息led1_id = mygpio_init(leds_node, "led1_gpio"); // 控制 GPIOled2_id = mygpio_init(leds_node, "led2_gpio"); // 控制 GPIOled3_id = mygpio_init(leds_node, "led3_gpio"); // 控制 GPIO/* printk("name=%s,value=%s\n", leds_node->properties->name, (char *)leds_node->properties->value); // 打印第一個屬性的名稱和值printk("name=%s,value=%s\n", leds_node->properties->next->name, (char *)leds_node->properties->next->value); // 打印第二個屬性的名稱和值printk("name=%s,value=%x %x\n", leds_node->properties->next->next->name, __be32_to_cpup((uint32_t *)leds_node->properties->next->next->value), __be32_to_cpup((uint32_t *)leds_node->properties->next->next->value + 1)); // 打印第三個屬性的名稱和值(無符號整數)// 解析設備樹的屬性binarry = of_find_property(leds_node, "binarry", &len); // 查找名為 "binarry" 的屬性if (!binarry) // 如果未找到屬性{printk("binarry property not found\n"); // 打印未找到屬性的消息return -ENODEV; // 返回錯誤碼}for (i = 0; i < len; i++) // 遍歷屬性值{printk("%02x ", *((unsigned char *)binarry->value + i)); // 打印屬性值的每個字節} */return 0; // 返回成功
}static void __exit leds_exit(void) // 模塊退出函數
{// 退出時執行的清理操作(當前為空)gpio_free(led1_id); // 釋放 GPIOgpio_free(led2_id); // 釋放 GPIOgpio_free(led3_id); // 釋放 GPIO//字符設備注銷device_destroy(led_class, MKDEV(led_major, 0)); // 注銷設備class_destroy(led_class); // 注銷類unregister_chrdev(led_major, "leds_control"); // 注銷字符設備printk("exit\n"); // 打印退出消息
}module_init(leds_init); // 指定模塊初始化函數
module_exit(leds_exit); // 指定模塊退出函數
MODULE_LICENSE("GPL"); // 指定模塊許可證為 GPL
MODULE_AUTHOR("Johnson"); // 指定模塊作者
MODULE_DESCRIPTION("leds driver"); // 指定模塊描述
MODULE_VERSION("V1.0"); // 指定模塊版本
應用程序 --> test_app.c
實現按1s間隔控制三個led燈亮滅
#include<stdlib.h>
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.h>
#include<string.h>
#include<sys/ioctl.h>
#include "leds.h" // 包含自定義頭文件int main(int argc,const char * argv[])
{int fd;int ret[3] = {1,2,3}; // 定義返回值int value; // 定義變量fd = open("/dev/leds_control", O_RDWR); // 打開設備文件if(fd < 0) // 如果打開設備文件失敗{perror("open"); // 打印錯誤信息return -1; // 返回錯誤碼}printf("open success\n"); // 打印打開設備文件成功的消息while(1){ioctl(fd, LED_ON, ret); // 打開 LED1ioctl(fd, LED_ON, ret+1); // 打開 LED2ioctl(fd, LED_ON, ret+2); // 打開 LED3sleep(1); // 等待 1 秒ioctl(fd, LED_OFF, ret); // 關閉 LED1ioctl(fd, LED_OFF, ret+1); // 關閉 LED2ioctl(fd, LED_OFF, ret+2); // 關閉 LED3sleep(1); // 等待 1 秒}return 0;
}
頭文件 --> leds.h
#ifndef __LEDS_H__
#define __LEDS_H__#define LED_ON _IOW('l', 1, int)
#define LED_OFF _IOW('l', 0, int)#endif