目录
前言
一、操作顺序
已有的基础:
- 硬件:正点原子Mini Linux开发板(EMMC 8GB)
- 内核:linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek.tar.bz2
- U-Boot:uboot-imx-rel_imx_4.1.15_2.1.0_ga_alientek.tar.bz2
- 配置网络文件系统:NFS
操作顺序:
- 修改设备树文件.dts
- 编译设备树文件生成.dtb文件
- 更新设备树文件.dtb
- 创建驱动工程文件
- beep.c:驱动程序
- beepApp.c:驱动应用程序
- Makefile
- c_cpp_properties.json
- 编译驱动模块.ko文件:
make -j16
- 编译beepApp.c:
arm-linux-gnueabihf-gcc beepApp.c -o beepApp
- 拷贝beep.ko和beepApp到NFS目录下
- 在目标机里面安装驱动模块(.ko文件):
modprobe beep.ko
- 在目标机里面测试驱动:
./beepApp /dev/beep 1 # 打开
- 在目标机里面卸载驱动:
rmmod beep.ko
- 完成。。。
二、代码模板
驱动模块代码模板:
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
// 设备结构体
struct xxx_dev {
dev_t devid; // 设备号
struct cdev cdev; // cdev
struct class *class; // 类
struct device *device; // 设备
int major; // 主设备号
int minor; // 次设备号
struct device_node *nd; // 设备节点
int xxx_gpio; // xxx所使用的GPIO编号
};
// 打开设备
static int xxx_open(struct inode *inode, struct file *filp)
{
// TODO
return 0;
}
// 向设备写数据
static ssize_t xxx_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
// TODO
return 0;
}
// 关闭/释放设备
static int xxx_release(struct inode *inode, struct file *filp)
{
// TODO
return 0;
}
// 设备操作函数
static struct file_operations xxx_fops = {
.owner = THIS_MODULE,
.open = xxx_open,
.write = xxx_write,
.release = xxx_release,
};
// 驱动入口函数
static int __init xxx_init(void)
{
// TODO
return 0;
}
// 驱动出口函数
static void __exit xxx_exit(void)
{
// TODO
}
module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("liefyuan");
驱动模块应用代码模板:
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[1];
if(argc != 3){
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
// 打开 beep 驱动
fd = open(filename, O_RDWR);
if(fd < 0){
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
// 要执行的操作:打开或关闭
databuf[0] = atoi(argv[2]);
// 向/dev/beep 文件写入数据
retvalue = write(fd, databuf, sizeof(databuf));
if(retvalue < 0){
printf("XXX Control Failed!\r\n");
close(fd); return -1;
}
// 关闭文件
retvalue = close(fd);
if(retvalue < 0){
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}
Makefile文件
KERNELDIR := /home/liefyuan/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH := $(shell pwd)
obj-m := chrdevbase.o
build : kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
c_cpp_properties.json
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/home/liefyuan/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/include",
"/home/liefyuan/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include",
"/home/liefyuan/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include/generated/"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "clang-x64"
}
],
"version": 4
}
三、模块代码
使用VSCode
- 建立驱动VSCode工程
建立驱动的VSCode工程
1、创建 VSCode 工程 在 Ubuntu 中创建一个目录用来存放 Linux 驱动程序,比如我创建了一个名为 Linux_Drivers 的目录来存放所有的 Linux 驱动。
在 1_chrdevbase 目录中新建 VSCode 工程,并且新建 chrdevbase.c 文件,完成以后 1_chrdevbase 目录中的文件
2、添加头文件路径 因为是编写Linux驱动,因此会用到Linux源码中的函数。我们需要在VSCode中添加Linux 源码中的头文件路径。打开 VSCode,按下“Crtl+Shift+P”打开 VSCode 的控制台,然后输入 “C/C++: Edit configurations(JSON) ”,打开 C/C++编辑配置文件:
打开以后会自动在.vscode 目录下生成一个名为 c_cpp_properties.json 的文件,此文件默认 内容如下所示:
c_cpp_properties.json 文件原内容
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
],
"defines": [],
"compilerPath": "/usr/bin/clang",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "clang-x64"
}
],
"version": 4
}
第 5 行的 includePath 表示头文件路径,需要将 Linux 源码里面的头文件路径添加进来,也 就是我们前面移植的 Linux 源码中的头文件路径。添加头文件路径以后的 c_cpp_properties.json 的文件内容如下所示
添加头文件路径后的 c_cpp_properties.json
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imxrel_imx_4.1.15_2.1.0_ga_alientek/include",
"/home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imxrel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include",
"/home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imxrel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include/gener ated/"
],
"defines": [], ......
}
],
"version": 4
}
第7~9行就是添加好的Linux头文件路径。分别是开发板所使用的Linux源码下的include、 arch/arm/include 和 arch/arm/include/generated 这三个目录的路径,注意,这里使用了绝对路径。
修改设备树
- 添加pinctrl节点
- 添加BEEP设备节点
- 检查 PIN 是否被其他外设使
设备树文件是放在Linux内核工程文件下的:/arch/arm/boot/dts/imx6ull-alientek-emmc.dts
1、添加 pinctrl 节点
I.MX6U-ALPHA开发板上的BEEP使用了SNVS_TAMPER1这个PIN,打开imx6ull-alientekemmc.dts,在 iomuxc 节点的 imx6ul-evk 子节点下创建一个名为“pinctrl_beep”的子节点,节点 内容如下所示:
SNVS_TAMPER1 pinctrl 节点
pinctrl_beep: beepgrp {
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x10B0 /* beep */
>;
};
第 3 行,将 SNVS_TAMPER1 这个 PIN 复用为 GPIO5_IO01 ,宏 MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 定义在 arch/arm/boot/dts/imx6ull-pinfunc-snvs.h 文件中。
2、添加 BEEP 设备数节点
1、添加设备树节点
在根节点“/”下创建 BEEP 节点,节点名为“beep”,节点内容如下:
创建 BEEP 蜂鸣器节点
beep {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-beep";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_beep>;
beep-gpio = <&gpio5 1 GPIO_ACTIVE_HIGH>;
status = "okay";
};
- pinctrl-0 属性设置蜂鸣器所使用的 PIN 对应的 pinctrl 节点。
- beep-gpio 属性指定了蜂鸣器所使用的 GPIO。
2、编译设备树文件
设备树文件是放在Linux内核工程文件下的:arch/arm/boot/dts/xxx.dts 所以在编译的时候就要在Linux内核工程的根目录下编译指定的dts文件:
make imx6ull-liefyuan-emmc.dtb
等编译完成后就会在arch/arm/boot/dts/ 目录下看到 .dtb文件了。(如果该目录下有同名文件可能编译不成功(make[1]: ‘xxxx’ is up to data))
3、更新设备树文件
上面编译出一个 imx6ull-liefyuan-emmc.dtb
文件,位于:arch/arm/boot/dts目录下,现在通过NFS更新设备树文件。
- uboot进入命令行模式
- 查看EMMC中的文件架构
- Uboot命令:
fatls mmc 1:1
- Uboot命令:
=> fatls mmc 1:1
6788696 zimage
38823 imx6ull-14x14-emmc-4.3-800x480-c.dtb
39655 imx6ull-14x14-emmc-hdmi.dtb
3 file(s), 0 dir(s)
从tft服务器下载设备树文件到内存(DDR):
=> tftp 83000000 imx6ull-liefyuan-emmc.dtb
Using FEC1 device
TFTP from server 192.168.0.120; our IP address is 192.168.0.121
Filename 'imx6ull-liefyuan-emmc.dtb'.
Load address: 0x83000000
Loading: ###
2.1 MiB/s
done
Bytes transferred = 38823 (97a7 hex)
从内存写入EMMC:
=> fatwrite mmc 1:1 83000000 imx6ull-liefyuan-emmc.dtb 0x97a7
writing imx6ull-liefyuan-emmc.dtb
38823 bytes written
设置设备树文件作为开机使用的文件:
=> setenv bootcmd 'fatload mmc 1:1 80800000 zimage; fatload mmc 1:1 83000000 imx6ull-liefyuan-emmc.dtb; bootz 80800000 - 83000000'
=> saveenv
Saving Environment to MMC...
Writing to MMC(1)... done
=> boot
3、检查 PIN 是否被其他外设使用
在蜂鸣器使用的PIN 为SNVS_TAMPER1,因此先检查PIN 为SNVS_TAMPER1 这个 PIN 有没有被其他的 pinctrl 节点使用,如果有使用的话就要屏蔽掉,然后再检查 GPIO5_IO01 这个 GPIO 有没有被其他外设使用,如果有的话也要屏蔽掉。
设备树编写完成以后使用“make dtbs”命令重新编译设备树,然后使用新编译出来的 imx6ull-alientek-emmc.dtb 文件启动 Linux 系统。启动成功以后进入“/proc/device-tree”目录中 查看“beep”节点是否存在,如果存在的话就说明设备树基本修改成功(具体还要驱动验证)
代码
- beep.c
- beep.App.c
- Makefile
- c_cpp_properties.json
c_cpp_properties.json
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/home/liefyuan/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/include",
"/home/liefyuan/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include",
"/home/liefyuan/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include/generated/"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "clang-x64"
}
],
"version": 4
}
Makefile
KERNELDIR := /home/liefyuan/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH := $(shell pwd)
obj-m := beep.o
build : kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
beep.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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : beep.c
作者 : liefyuan
版本 : V1.0
描述 : 蜂鸣器驱动程序
其他 : 无
论坛 : www.liefyuan.top
日志 : 初版 V1.0 2019/11/29 for liefyuan
***************************************************************/
#define BEEP_CNT 1 /* 设备号个数 */
#define BEEP_NAME "beep" /* 名字 */
#define BEEPOFF 0 /* 关蜂鸣器 */
#define BEEPON 1 /* 开蜂鸣器 */
// 设备结构体
struct beep_dev {
dev_t devid; // 设备号
struct cdev cdev; // cdev
struct class *class; // 类
struct device *device; // 设备
int major; // 主设备号
int minor; // 次设备号
struct device_node *nd; // 设备节点
int beep_gpio; // beep所使用的GPIO编号
};
struct beep_dev beep; /* beep 设备 */
// 打开设备
static int beep_open(struct inode *inode, struct file *filp)
{
filp->private_data = &beep; // private data
return 0;
}
// 向设备写数据
static ssize_t beep_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int ret;
unsigned char databuf[1];
unsigned char beepstat;
struct beep_dev *dev = filp->private_data;
ret = copy_from_user(databuf, buf, cnt);
if(ret < 0)
{
printk("kernel write failed!\r\n");
return -EFAULT;
}
beepstat = databuf[0];
if(beepstat == BEEPON)
{
gpio_set_value(dev->beep_gpio, 0); // open beep
}
else if (beepstat == BEEPOFF)
{
gpio_set_value(dev->beep_gpio, 1); // close beep
}
return 0;
}
// 关闭/释放设备
static int beep_release(struct inode *inode, struct file *filp)
{
// TODO
return 0;
}
// 设备操作函数
static struct file_operations beep_fops = {
.owner = THIS_MODULE,
.open = beep_open,
.write = beep_write,
.release = beep_release,
};
// 驱动入口函数
static int __init beep_init(void)
{
int ret = 0;
// set beep map gpio
// 1.get device node:beep
beep.nd = of_find_node_by_path("/beep");
if(beep.nd == NULL)
{
printk("beep node not find!\r\n");
return -EFAULT;
}
else
{
printk("beep node find!\r\n");
}
// 2.get device tree gpio proper,get beep used gpio num
beep.beep_gpio = of_get_named_gpio(beep.nd, "beep-gpio", 0);
if(beep.beep_gpio < 0)
{
printk("can't get beep-gpio");
return -EFAULT;
}
printk("led-gpio num = %d\r\n", beep.beep_gpio);
// 3.set GPIO5_IO01 output mode, default state is high level beep default closed
ret = gpio_direction_output(beep.beep_gpio, 1);
if(ret < 0)
{
printk("can't set gpio!\r\n");
}
// register char device drive
// 1.create device num
if(beep.major) // if define device num
{
beep.devid = MKDEV(beep.major, 0);
register_chrdev_region(beep.devid, BEEP_CNT, BEEP_NAME);
}
else // no define device num
{
alloc_chrdev_region(&beep.devid, 0, BEEP_CNT, BEEP_NAME);
beep.major = MAJOR(beep.devid); // get main device num
beep.minor = MINOR(beep.devid); // get sub device num
}
printk("beep major = %d, minor = %d\r\n", beep.major, beep.minor);
// 2.init cdev
beep.cdev.owner = THIS_MODULE;
cdev_init(&beep.cdev, &beep_fops);
// 3.add cdev
cdev_add(&beep.cdev, beep.devid, BEEP_CNT);
// 4.create class
beep.class = class_create(THIS_MODULE, BEEP_NAME);
if(IS_ERR(beep.class))
{
return PTR_ERR(beep.class);
}
// 5.create device
beep.device = device_create(beep.class, NULL, beep.devid, NULL, BEEP_NAME);
if(IS_ERR(beep.device))
{
return PTR_ERR(beep.device);
}
return 0;
}
// 驱动出口函数
static void __exit beep_exit(void)
{
// dismiss char device driver
cdev_del(&beep.cdev);
unregister_chrdev_region(beep.devid, BEEP_CNT);
device_destroy(beep.class, beep.devid);
class_destroy(beep.class);
}
module_init(beep_init);
module_exit(beep_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("liefyuan");
beepApp.c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[1];
if(argc != 3){
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
// 打开 beep 驱动
fd = open(filename, O_RDWR);
if(fd < 0){
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
// 要执行的操作:打开或关闭
databuf[0] = atoi(argv[2]);
// 向/dev/beep 文件写入数据
retvalue = write(fd, databuf, sizeof(databuf));
if(retvalue < 0){
printf("BEEP Control Failed!\r\n");
close(fd); return -1;
}
// 关闭文件
retvalue = close(fd);
if(retvalue < 0){
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}
设置从emmc上启动Linux kernel和设备树
=> setenv bootcmd 'fatload mmc 1:1 80800000 zimage; fatload mmc 1:1 83000000 imx6ull-liefyuan-emmc.dtb; bootz 80800000 - 83000000'
=> saveenv
Saving Environment to MMC...
Writing to MMC(1)... done
=> boot
设置从nfs上启动根文件系统
setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.0.120:/home/liefyuan/linux/nfs/rootfs,proto=tcp rw ip=192.168.0.121:192.168.1.120:192.168.0.1:255.255.255.0::eth0:off' //设置 bootargs
saveenv
测试
- 加载beep.ko模块:
- 第一次加载驱动模块的时候先执行这个命令:
depmod
- 加载驱动模块:
modprobe beep.ko
- 第一次加载驱动模块的时候先执行这个命令:
/lib/modules/4.1.15/kernel/drivers/beep # depmod
/lib/modules/4.1.15/kernel/drivers/beep # modprobe beep.ko
beep node find!
led-gpio num = 129
beep major = 249, minor = 0
/lib/modules/4.1.15/kernel/drivers/beep # ./beepApp /dev/beep 1
/lib/modules/4.1.15/kernel/drivers/beep # ./beepApp /dev/beep 0
完成beep驱动。