一、创建工程
1、添加设备树节点
1)添加设备节点
在根节点下创建 led 设备节点:
gpioled {
compatible = "gpioled_test";
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&gpioled>;
gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
};
2)添加pinctrl节点
在 iomuxc 节点下的子节点 imx6ul-evk 中添加 pinctrl 节点:
gpioled: ledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10b0
>;
};
2、添加设备结构体
/* 设备结构体 */
struct gpioled_dev {
dev_t devid; //设备号
int major; //主设备号
int minor; //次设备号
struct cdev cdev; //设备结构体
struct class *class; //类
struct device *device; //设备
struct device_node *nd; //设备树节点
struct property *compatible; //compatible属性
const char *status; //status属性
int led_gpio; //gpio编号
};
struct gpioled_dev gpioled;
3、编写加载和卸载注册函数
加载和卸载注册函数如下:
module_init(dtsled_init); //注册模块加载函数
module_exit(dtsled_exit); //注册模块卸载函数
入口函数:
#define DEVICE_CNT 1
#define DEVICE_NAME "gpioled"
/* 入口函数 */
static int __init gpio_led_init(void)
{
int ret = 0;
/* 设备号处理 */
gpioled.major = 0;
if(gpioled.major){ //设置了主设备号
gpioled.devid = MKDEV(gpioled.major, 0);
ret = register_chrdev_region(gpioled.devid, DEVICE_CNT, DEVICE_NAME);
if(ret < 0){ //注册设备号失败
printk("unable to register devid\r\n");
ret = -EINVAL;
goto fail_register_devid;
}
}else{ //没有设置主设备号
ret = alloc_chrdev_region(&gpioled.devid, 0, DEVICE_CNT, DEVICE_NAME);
if(ret < 0){ //申请设备号失败
printk("unable to alloc devid\r\n");
ret = -EINVAL;
goto fail_alloc_devid;
}
gpioled.major = MAJOR(gpioled.devid);
gpioled.minor = MINOR(gpioled.devid);
printk("The major is %d\r\nThe minor is %d\r\n", gpioled.major, gpioled.minor);
}
/* 注册设备 */
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev, &gpioled_fops);
ret = cdev_add(&gpioled.cdev, gpioled.devid, 1);
if(ret < 0){ //注册设备失败
printk("fail to add cdev\r\n");
ret = -EINVAL;
goto fail_cdev;
}
/* 自动创建设备节点 */
gpioled.class = NULL;
gpioled.device = NULL;
gpioled.class = class_create(THIS_MODULE, DEVICE_NAME);
if(gpioled.class == NULL){ //创建类失败
printk("fail to create class\r\n");
ret = -EINVAL;
goto fail_create_class;
}
gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, DEVICE_NAME);
if(gpioled.class == NULL){ //创建类失败
printk("fail to create device\r\n");
ret = -EINVAL;
goto fail_create_device;
}
/* 获取设备树节点 */
gpioled.nd = of_find_node_by_name(NULL, "gpioled");
if(gpioled.nd == NULL){ //获取设备树节点失败
printk("fail to find node\r\n");
ret = -EINVAL;
goto fail_find_nd;
}
/* 获取compatible属性 */
gpioled.compatible = of_find_property(gpioled.nd, "compatible", NULL);
if(gpioled.compatible == NULL){ //获取compatible属性失败
printk("fail to find compatible\r\n");
ret = -EINVAL;
goto fail_find_compatible;
}else{
printk("The value of compatible is %s\r\n", (char*)gpioled.compatible->value);
}
/* 获取status属性 */
ret = of_property_read_string(gpioled.nd, "status", &gpioled.status);
if(ret < 0){ //获取status失败
printk("fail to find status\r\n");
ret = -EINVAL;
goto fail_find_status;
}else{
printk("The value of status is %s\r\n", gpioled.status);
}
/* 获取gpio编号 */
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "gpios", 0);
if(gpioled.led_gpio < 0){ //获取gpio编号失败
printk("fail to find gpio_nd\r\n");
ret = -EINVAL;
goto fail_find_status;
}else{
printk("find gpio_nd\r\n");
}
/* 注册gpio */
ret = gpio_request(gpioled.led_gpio, "led_gpio");
if(ret != 0){ //注册gpio失败
printk("fail to request gpio\r\n");
ret = -EINVAL;
goto fail_requst_gpio;
}
/* 设置gpio为输出 */
ret = gpio_direction_output(gpioled.led_gpio, 1);
if(ret < 0){ //设置失败
printk("fail to set gpio output\r\n");
return -EINVAL;
}
printk("gpioled init\r\n");
return 0;
fail_find_nd:
fail_find_compatible:
fail_find_status:
fail_requst_gpio:
fail_register_devid:
fail_alloc_devid:
fail_cdev:
unregister_chrdev_region(gpioled.devid, DEVICE_CNT);
fail_create_class:
cdev_del(&gpioled.cdev);
unregister_chrdev_region(gpioled.devid, DEVICE_CNT);
fail_create_device:
class_destroy(gpioled.class);
cdev_del(&gpioled.cdev);
unregister_chrdev_region(gpioled.devid, DEVICE_CNT);
return ret;
}
出口函数:
/* 出口函数 */
static void __exit gpio_led_exit(void)
{
led_switch(LED_OFF);
/* 释放gpio */
gpio_free(gpioled.led_gpio);
/* 删除设备 */
device_destroy(gpioled.class, gpioled.devid);
/* 删除类 */
class_destroy(gpioled.class);
/* 注销字符设备 */
cdev_del(&gpioled.cdev);
/* 注销设备号 */
unregister_chrdev_region(gpioled.devid, DEVICE_CNT);
}
3、编写设备的具体操作函数
/* open函数 */
static int gpioled_open(struct inode *inode, struct file *filp)
{
return 0;
}
/* release函数 */
static int gpioled_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* write函数 */
static ssize_t dtsled_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
int ret = 0;
u8 led_state[1];
ret = copy_from_user(led_state, buf, count);
if(ret < 0){
printk("recieve led_state fail\r\n");
return -EINVAL;
}
if(led_state[0] == LED_ON){
led_switch(LED_ON);
}
else{
led_switch(LED_OFF);
}
return 0;
}
/* 操作函数集合 */
static const struct file_operations gpioled_fops = {
.owner = THIS_MODULE,
.open = gpioled_open,
.release = gpioled_release,
.write = dtsled_write,
};
4、添加头文件及创建虚拟地址指针
参考 linux 内核的驱动代码时,找到可能用到的头文件,添加进工程。在调用系统调用函数或库函数时,在终端使用 man 命令可查看调用的函数需要包含哪些头文件。
man 命令数字含义:1:标准命令 2:系统调用 3:库函数
添加以下头文件:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#define DEVICE_CNT 1
#define DEVICE_NAME "gpioled"
#define LED_ON 1
#define LED_OFF 0
5、添加 License 和作者信息
驱动的 License 是必须的,缺少的话会报错,在文件最末端添加以下代码:
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lzk");
6、编写led状态切换函数
/* LED状态切换函数 */
void led_switch(int led_state)
{
if(led_state == LED_ON){
gpio_set_value(gpioled.led_gpio, 0); //使用gpio子系统的API函数
}else{
gpio_set_value(gpioled.led_gpio, 1);
}
}
二、编写测试应用程序
在驱动文件夹下创建 ledAPP.c 文件:
#include <sys/types.h> // open 函数所需头文件
#include <sys/stat.h> //open 函数所需头文件
#include <fcntl.h> //open 函数所需头文件
#include <stdio.h> //printf 函数所需头文件
#include <unistd.h> //read write close 函数所需头文件
#include <string.h> //memcpy 函数所需头文件
#include <stdlib.h> //atoi 函数所需头文件
#define LED_ON 1
#define LED_OFF 0
/*
应用程序使用 arm-linux-gnueabihf-gcc ledAPP.c -o ledAPP来编译,生成可执行文件
执行应用程序时使用命令行:./ledAPP /dev/ledAPP 1
命令信息保存在 main 函数的两个入口参数中
argc:为命令参数个数,为 3
argv:为命令各个参数的具体内容:
argv[0]:打开应用程序
argv[1]:open读取的文件,驱动文件都在/dev目录下
argv[2]:定义读写功能,1 为开灯 ,0为关灯
命令所带参数可自行定义
*/
int main(int argc, char *argv[])
{
int fd = 0; //文件描述符,读取文件之前要用open函数打开文件,打开成功后得到文件描述符
int ret = 0; //read write函数返回值
unsigned char led_state[1]; //保存led状态
char *filename; //open函数读取的文件,为argv[2]
if(argc != 3) //如果命令参数不等于3,表明输入命令格式不对
{
printf("missing parameter!\r\n");
return -1;
}
filename = argv[1]; //获取驱动文件名
fd = open(filename , O_RDWR); //打开驱动文件,O_RDWR表明读写模式(man 2 open 查看具体)
if(fd < 0) //如果文件描述符小于0,则表明打开文件失败
{
printf("open file %s failed\r\n", filename);
return -1;
}
/*
写操作
命令行传递的参数均为字符,atoi函数把字符数字转化成整型
*/
if(atoi(argv[2]) == 1){
led_state[0] = LED_ON; //开灯
ret = write(fd, led_state, sizeof(led_state)); //写入led状态
if(ret < 0){
printf("write file %s failed\r\n", filename);
return -1;
}
}
else{
led_state[0] = LED_OFF; //关灯
ret = write(fd, led_state, sizeof(led_state)); //写入led状态
if(ret < 0){
printf("write file %s failed\r\n", filename);
return -1;
}
}
ret = close(fd); //关闭文件
if(ret < 0) //返回值小于0关闭文件失败
{
printf("close file %s failed\r\n", filename);
return -1;
}
return 0;
}
三、编译和测试
1、编写Makefile,编译驱动程序
驱动程序源码需要编译成.ko模块,创建Makefile:
KERNELDIR := /home/liuzhikai/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga
CURRENT_PATH := $(shell pwd)
obj-m := gpioled.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
-
第1行,KERNELDIR 表示开发板所使用的 Linux 内核源码目录,使用绝对路径。
-
第2行,CURRENT_PATH 表示当前路径。
-
第3行,obj-m 表示将 led.c 这个文件编译为模块。
-
第5行,默认目标为 kernel_modules。
-
第8行,具体的编译命令,后面的 modules 表示编译模块,-C表示将当前的工作目录切换到指定目录中。M表示模块源码目录。“make modules”命令中加入 M=dir 以后程序会自动到指定的 dir 目录中读取模块源码并将其编译出 .ko文件。
编译成功以后会生成一个 led.ko 文件,这个文件就是驱动模块。
2、编译驱动程序
测试 APP 要在 ARM 开发板上运行,所以使用交叉编译器编译:
arm-linux-gnueabihf-gcc ledAPP.c -o ledAPP
编译完成生成 ledAPP 的可执行程序。
3、运行测试
linux 系统选择网络启动,并且用 nfs 挂载根文件系统,U-Boot 设置如下:
bootcmd 的值:
tftp 80800000 zImage;tftp 83000000 imx6ull-alientek-emmc.dtb;bootz 80800000 - 83000000
bootargs 的值:
console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.1.138:/home/liuzhikai/linux/nfs/rootfs ip=192.168.1.123:192.168.1.138:192.168.1.2:255.255.255.0::eth0:off
将 dtsled.ko 和 ledAPP 拷贝到以下目录(不存在的目录创建):
sudo cp dtsled.ko ledApp /home/liuzhikai/linux/nfs/rootfs/lib/modules/4.1.15/ -f
使用如下命令加载驱动模块:
depmod
modprobe dtsled.ko //加载驱动模块
./ledAPP /dev/dtsled 1 //执行应用程序,打开led灯
./ledAPP /dev/dtsled 0 //执行应用程序,关闭led灯
rmmod dtsled.ko //卸载驱动模块