我们来开始我们Linux下第一个platform设备的开发,通过linux 应用程序来控制开发板上的一个gpio led灯的亮灭.
首先介绍一下平台总线设备的开发过程,linux中采用了设备和驱动分离的思想,通过总线将设备和驱动匹配,所以这里涉及三个概念:总线(bus),设备(device)和驱动(driver),总线就像是红娘月老,做设备和驱动匹配的工作,比起其他内核中的总线比如i2c,spi,usb,在这里platform是个虚拟的总线.
因为分为驱动代码和应用代码,
我们先写驱动层代码吧:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/ide.h>
#include <linux/fcntl.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/mach/map.h>
struct led_data {//led设备属性
int major;//主设备号
int minor;//次设备号
dev_t devid;//设备id
struct cdev cdev;//字符设备
struct class *class;//设备类
struct device *device;//设备
struct device_node *node;//设备节点
int pin;
};
static struct led_data led;//声明一个驱动实例
static int led_open(struct inode *inode, struct file *file){//驱动层打开文件
file->private_data = &led;//将led实例保存为私有数据
return 0;
}
static ssize_t led_write(struct file *file, const char __user *buf, size_t size, loff_t *lof){
char data[2];//两个字节的buffer
int res = 0;
res = copy_from_user(data , buf ,size);//从用户空间拷贝过来buffer
if(res<0){
printk("kernel write err\n");
return -EFAULT;
}else{
printk("copy_from_user is ok, buf = %d\n",data[0]);
}
if(data[0]==1){//如果用户程序写1则灯亮起来,否则灭
gpio_set_value(led.pin,1);
}else if(data[0]==0){
gpio_set_value(led.pin,0);
}
return 0;
}
static int led_release(struct inode *inode, struct file *file){
return 0;//啥也不做,可以不用声明这个函数
}
static const struct file_operations led_ops = {
.owner = THIS_MODULE,
.open = led_open,//注册文件控制函数
.write = led_write,
.release = led_release,
};
static const struct of_device_id led_of_match[] = {
{.compatible = "myz-led"},//通过compatible属性来匹配设备树的gpio-led
{ },
};
static int led_probe(struct platform_device *pdev){//当设备和驱动匹配成功就会执行probe函数
alloc_chrdev_region(&led.devid,0,1,"led-myz");//申请字符设备
led.major = MAJOR(led.devid);//生成主设备号
cdev_init(&led.cdev,&led_ops);//初始化字符设备并注册文件操作结构体
cdev_add(&led.cdev,led.devid,1);//添加一个字符设备
led.class=class_create(THIS_MODULE,"led-myz");//产生类
if(IS_ERR(led.class)){
return PTR_ERR(led.class);
}
led.device = device_create(led.class,NULL,led.devid,NULL,"led-myz");//生成设备
if(IS_ERR(led.device)){
return PTR_ERR(led.device);
}
led.node = of_find_node_by_path("/gpioled");//通过设备树路径找到gpio的节点
if(led.node == NULL){
printk("gpioled node is not find\n");
return -EINVAL;
}else{
printk("gpioled node is find\n");
}
led.pin = of_get_named_gpio(led.node,"led-gpio",0);//通过节点下的gpio名字获取gpio引脚
if(led.pin<0){
printk("led-pin is not find \n");
return -EINVAL;
}else{
printk("led-pin is find \n");
}
gpio_request(led.pin,"led0");//申请gpio
gpio_direction_output(led.pin,1);//设置gpio 为输出
printk("led- probe over \n");
return 0;
}
static int led_remove(struct platform_device *pdev){//当驱动注销时就会执行remove函数
gpio_set_value(led.pin,0);//设置引脚为低
cdev_del(&led.cdev);//字符设备删除
unregister_chrdev_region(led.devid,1);//注销led驱动
device_destroy(led.class,led.devid);//摧毁类
class_destroy(led.class);//摧毁驱动
return 0;
}
static struct platform_driver led_driver ={
.probe = led_probe,//注册probe和remove函数
.remove = led_remove,
.driver = {
.name = "led",
.of_match_table = led_of_match,//注册of_match结构体
},
};
static int __init led_init(void){
return platform_driver_register(&led_driver);//注册platform驱动
}
static void __exit led_exit(void){
platform_driver_unregister(&led_driver);//注销platform驱动
}
module_init(led_init);//insmod的时候会调用这个函数
module_exit(led_exit);//rmmod的时候会调用这个函数
MODULE_LICENSE("GPL");//驱动协议必不可少
在设备树文件xxx.dts中的设备树根目录中添加
gpioled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "myz-led";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
led-gpio = <&gpio5 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
在linux内核根目录下执行
make dtbs
生成xxx.dtb文件,重启开发板系统加载xxx.dtb文件到内核中
然后编写应用层代码:
#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[]){
char *filename;
int fd ,res;
char data[2]={0};
if(argc != 3){
printf("err usage\n");
return -1 ;
}
filename = argv[1];
fd = open(filename,O_RDWR);
if(fd<0){
printf("%s open failed\n",filename);
return -1;
}
data[0] = atoi(argv[2]);
res = write(fd,data,sizeof(data));
if(res<0){
printf("file %s write err\n",filename);
close(fd);
return -1;
}
res = close(fd);
if(res<0){
printf("file %s close err\n",filename);
return -1;
}
}
接着写一个简单的makefile用来编译这两个代码
KERNELDIR :=/home/mayunzhi/linux/Linux-4.9.88
CURRENT_PATH :=$(shell pwd)
ARM-CC :=arm-linux-gnueabihf-gcc
APP :=app
DRV :=led
obj-m :=$(DRV).o
build:kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
rm -f ./app
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
app:
$(ARM-CC) $(APP).c -o $(APP)
mv:
mv ./app *.ko /home/mayunzhi/linux/nfs/rootfs/lib/modules/4.1.15-2.1.0+g30278ab/
首先执行make
编译驱动生成led.ko
然后执行make app
生成app可执行文件
再通过make mv
将ko文件和app文件移动到开发板nfs文件系统中,从开发版的终端进入饱含着两个文件的文件夹,执行
insmod led.ko
查看led驱动
lsmod
接着运行app程序,用法如下:
#开灯
./app /dev/led-myz 1
#开发板上的灯亮了起来
#关灯
./app /dev/led-myz 0
#开发板上的灯灭了
最后卸载驱动命令如下;
rmmod led.ko