基于上一节的字符驱动编程框架,控制led灯
(一) GPIO库函数
1)申请GPIO管脚
int gpio_request(unsigned gpio, const char *label)
gpio, 管脚编号
label,管脚名称
返回值, 0 申请成功 非0 申请失败
2)设置GPIO管脚为输入或者输出模式
int gpio_direction_input(unsigned gpio)
//vaule,默认电平非0 高电平 反之低电平
int gpio_direction_output(unsigned gpio, int value)
3)设置/获取管脚的电平状态
int gpio_get_value(unsigned gpio)
//非0高电平,0低电平
void gpio_set_value(unsigned gpio, int value)
4)释放GPIO管脚
void gpio_free(unsigned gpio)
注:将内核中再带的LED驱动裁剪掉
cd kernel
make menuconfig
Device Drivers —>
-*- LED Support —>
< > LED Support for GPIO connected LEDs
< > PWM driven LED Support
[ ] LED Trigger support
make uImage
让开发板使用新内核
(二) file_operations
open = led_open:返回值,只有为0,才表示成功打开,比如在该例子中,如果在led_open中加入设置led管脚电平逻辑使led发光,如果设置失败(总之就是在led_open函数中,出现了错误),就应该返回一个合适的errno值,如 -EAGAIN
盲点:如果在led_open函数中,返回值永远为0,用户空间调用open函数就会永远打开成功吗?答案是否定的。比如no such file。当要open的设备文件不存在时,是执行不到led_open这里的,在某个环节就返回了。
release:对应用户空间的close函数
ssize_t (*write) (struct file *file, const char __user *buf, size_t len, loff_t *off):对应系统调用write(fd,buf,len),buf与buf对应,len与len对应
read:对应系统调用read,对应关系与write相同
long (*unlocked_ioctl) (struct file *, unsigned int cmd, unsigned long):对应系统调用ioctl,除第一个参数外,其余参数一一对应
关于ioctl的cmd字段
相关定义在<asm-generic/ioctl.h>中,文档说明在/home/cjl/tcar/kernel/Documentation/ioctl路径下的ioctl-decoding.txt和ioctl-number.txt(type使用情况的说明文档)随着内核版本的更迭,其编码可能是不一样的
在我的内核版本中,文件如下
ioctl-decoding.txt
To decode a hex IOCTL code:
Most architectures use this generic format, but check
include/ARCH/ioctl.h for specifics, e.g. powerpc
uses 3 bits to encode read/write and 13 bits for size.
bits meaning
31-30 00 - no parameters: uses _IO macro 也叫访问模式,access mode,或者方向,direction,简写dir
10 - read: _IOR
01 - write: _IOW
11 - read/write: _IOWR
29-16 size of arguments,size字段
15-8 ascii character supposedly unique to each driver,也叫幻数,type字段
7-0 function # 也叫序数,numberic字段,简写nr
So for example 0x82187201 is a read with arg length of 0x218,
character 'r' function 1. Grepping the source reveals this is:
#define VFAT_IOCTL_READDIR_BOTH _IOR('r', 1, struct dirent [2])
使用时用ioctl.h中的宏,将cmd可能的值定义到一个头文件中,即可驱动编程使用,也可用来给使用此驱动的人使用。
注意,dir(access mode)描述的方向是站在应用层的角度描述的。即IOR表示应用层要从驱动读数据。
(三)代码
led_cmd.h
用来定义ioctl命令
/*
* 经查ioctl-number.txt,0xFE还没有被占用
* */
#ifndef __LED_CMD_H__
#define __LED_CMD_H__
#include <linux/ioctl.h>
/*
* 如果为该文件增加cmd,注意要修改IOC_CMD_MAX_NR的值
* */
#define IOC_CMD_LED_TYPE 0xFE
#define IOC_CMD_MAX_NR 2
#define NR_LED_ON 1
#define NR_LED_OFF 2
#define IOC_LED_ON _IOW(IOC_CMD_LED_TYPE, NR_LED_ON, int)
#define IOC_LED_OFF _IOW(IOC_CMD_LED_TYPE, NR_LED_OFF, int)
#endif //__LED_CMD_H__
led_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <mach/platform.h> //PAD_GPIO_A/B/C/D
#include <linux/uaccess.h> //copy_to/from_user
#include <linux/gpio.h> //GPIO's lib function
#include <linux/cdev.h> //cdev_init/add/del etc...
#include <linux/fs.h> //alloc/register/unregister_chrdev_regsion
#include <linux/device.h> //class/device_create/destroy
#include <linux/ioctl.h> //ioctl number macro
#include <linux/vmalloc.h> //vmalloc
#include "led_cmd.h"
#define LED_BASEMINOR 0
#define LED_DEV_NUM 1
#define LED_ERROR(fmt, args...)\
printk(KERN_ERR "%s:%d: " fmt "\n", __func__, __LINE__, ##args)
MODULE_LICENSE("GPL");
dev_t led_dev;
struct cdev led_cdev;
struct class *cls_MYLEDS = NULL;
//led管脚描述
typedef struct led_desc{
int gpio;
char *name;
}led_desc_t;
static const led_desc_t leds[] = {
{PAD_GPIO_C + 12, "led1"},
{PAD_GPIO_C + 7, "led2"},
{PAD_GPIO_C + 11, "led3"},
{PAD_GPIO_B + 26, "led4"},
};
int stat = 0;//记录当前led等状态,全灭为0,否则为1
static int led_open(struct inode *inod, struct file *fil){
printk("<1>" "enter %s ", __func__);
printk("<1>" "可以在此处实现开关灯,不过并没有在此函数中实现\n");
return 0;//返回0表示打开成功,需要返回其他的错误应查阅源码中的宏定义
}
static int led_release(struct inode *inode, struct file *file){
printk("<1>" "enter %s ", __func__);
printk("<1>" "可以在此处实现开关灯,不过并没有在此函数中实现\n");
return 0;//当需要返回错误值时,查阅内核源码
}
/*
* 将led灯的状态写到用户空间内存buf中,全灭返回0,否则返回1
* 用户空间调用时,需固定读取字节数为sizeof(int),否则返回参数无效
* */
static ssize_t led_read(struct file *file, char __user *buf, size_t len, loff_t *offset){
int retval = 0;
if(len != sizeof(int))
return -EINVAL;
retval = copy_to_user(buf, &stat, sizeof(stat));
return (sizeof(stat) - retval);
}
/*
* 将led灯置为全亮非0或者全灭0
* 用户空间调用write时,写入的数据必须是四字节的int
*/
ssize_t led_write(struct file *file, const char __user *buf, size_t len, loff_t *offset){
int i = 0;
int cmd = 0;
int retval = 0;
if(len != sizeof(int))
return -EINVAL;
retval = copy_from_user(&cmd, buf, len);
for(i=0; i<ARRAY_SIZE(leds); i++){
gpio_set_value(leds[i].gpio, !!!cmd);
}
stat = !!cmd;
return (len - retval);
}
/*
* 控制每个灯的开关
* data:指针,读取该指针指向地址的四字节数据作为int型,表示leds的索引(从0开始)
* */
long led_ioctl(struct file *file, unsigned int cmd, unsigned long data){
int retval = 0;
void *user_data = NULL;
if(_IOC_TYPE(cmd) != IOC_CMD_LED_TYPE){
retval = -EINVAL;
goto cmd_type_error;
}
if(_IOC_NR(cmd) > IOC_CMD_MAX_NR || _IOC_NR(cmd) <= 0){
retval = -EINVAL;
goto cmd_nr_error;
}
//判断cmd中的数据流向
switch(_IOC_DIR(cmd)){
case _IOC_NONE:
retval = -EINVAL;
goto cmd_dir_error;
case _IOC_READ:
retval = -EINVAL;
goto cmd_dir_error;
case _IOC_WRITE://如果是从用户空间流向驱动
//vmalloc动态申请内存动态分配cmd中size大小的内存
user_data = vmalloc(_IOC_SIZE(cmd));
if(!user_data){
retval = -ENOMEM;
goto have_no_mem_store_user_data;
}
//从用户空间将数据拷贝到内核空间
if(copy_from_user(user_data, (void *)data, _IOC_SIZE(cmd))){
retval = -EFAULT;
goto copy_user_data_error;
}
break;
case (_IOC_WRITE | _IOC_READ):
retval = -EINVAL;
goto cmd_dir_error;
}
//判断是led的哪种命令
switch(_IOC_NR(cmd)){
case NR_LED_ON:
if(*((int *)user_data) > (ARRAY_SIZE(leds)-1) || *((int *)user_data) < 0){
retval = -EINVAL;
goto user_data_value_error;
}
gpio_set_value(leds[*((int *)user_data)].gpio, 0);
break;
case NR_LED_OFF:
if(*((int *)user_data) > (ARRAY_SIZE(leds)-1) || *((int *)user_data) < 0){
retval = -EINVAL;
goto user_data_value_error;
}
gpio_set_value(leds[*((int *)user_data)].gpio, 1);
break;
}
vfree(user_data);
user_data = NULL;
return 0;
user_data_value_error:
copy_user_data_error:
vfree(user_data);
user_data = NULL;
have_no_mem_store_user_data:
cmd_dir_error:
cmd_nr_error:
cmd_type_error:
return retval;
}
struct file_operations led_ops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.read = led_read,
.write = led_write,
.unlocked_ioctl = led_ioctl,
};
static int __init led_drv_init(void){
int ret = 0;
int i = 0;//循环变量
struct device *err = NULL;
//申请设备号
ret = alloc_chrdev_region(&led_dev, LED_BASEMINOR, LED_DEV_NUM, "myleds");
if(0 != ret){
LED_ERROR("alloc char device number failed...");
goto alloc_chrdev_region_error;
}
//初始化cdev
cdev_init(&led_cdev, &led_ops);
//向系统中添加一个字符设备
ret = cdev_add(&led_cdev, led_dev, LED_DEV_NUM);
if(0 != ret){
LED_ERROR("add a char device to system failed...");
goto cdev_add_error;
}
//在/sys/class下创建MYLEDS类
cls_MYLEDS = class_create(THIS_MODULE, "MYLEDS");
if(IS_ERR(cls_MYLEDS)){
LED_ERROR("create a class in /sys/class failed...");
ret = (int)cls_MYLEDS;
goto class_create_error;
}
//在MYLEDS类下创建myleds
err = device_create(cls_MYLEDS, NULL, led_dev, NULL, "myleds");
if(IS_ERR(err)){
LED_ERROR("create a device in /sys/class/MYLEDS failed...");
ret = (int)err;
goto device_create_error;
}
//申请GPIO管脚
for(i=0; i<ARRAY_SIZE(leds); i++){
ret = gpio_request(leds[i].gpio, leds[i].name);
if(ret){
LED_ERROR("Fail to request busy gpio PIN %d\n", leds[i].gpio);
goto gpio_request_error;
}
}
return 0;
gpio_request_error:
for(;i>0; i--){
gpio_free(leds[i-1].gpio);
}
device_destroy(cls_MYLEDS, led_dev);
device_create_error:
class_destroy(cls_MYLEDS);
class_create_error:
cdev_del(&led_cdev);
cdev_add_error:
unregister_chrdev_region(led_dev, LED_DEV_NUM);
alloc_chrdev_region_error:
return ret;
}
static void __exit led_drv_exit(void){
int i = 0;
for(i=0; i<ARRAY_SIZE(leds); i++){
printk("<1>" "free gpio %s\n", leds[i].name);
gpio_free(leds[i].gpio);
}
device_destroy(cls_MYLEDS, led_dev);
class_destroy(cls_MYLEDS);
cdev_del(&led_cdev);
unregister_chrdev_region(led_dev, LED_DEV_NUM);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
test.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include "led_cmd.h"
int main(void){
int stat = 0;
int all_on = 1;
int all_off = 0;
char str_cmd[32] = {0};
char led_cmd[8] = {0};
int index = 0;
int sscanfret = 0;
int fd = 0;
fd = open("/dev/myleds", O_RDWR);
if(-1 == fd){
perror("open");
return fd;
}
read(fd, &stat, sizeof(int));
printf("current status %d\n", stat);
printf("writing all leds on...\n");
write(fd, &all_on, sizeof(int));
read(fd, &stat, sizeof(int));
printf("current status %d\n", stat);
sleep(5);
printf("writing all leds off...\n");
write(fd, &all_off, sizeof(int));
read(fd, &stat, sizeof(int));
printf("current status %d\n", stat);
printf("\ntest ioctl until enter \"exit\"...\n");
while(1){
fgets(str_cmd, sizeof(str_cmd), stdin);
sscanfret = sscanf(str_cmd, "%s %d", led_cmd, &index);
if(!strcmp("exit", led_cmd)){
break;
}
if(2 != sscanfret){
printf("<led_on/led_off> <0/1/2/3>\n");
continue;
}
if(!strcmp("led_on", led_cmd)){
ioctl(fd, IOC_LED_ON, &index);
}else if(!strcmp("led_off", led_cmd)){
ioctl(fd, IOC_LED_OFF, &index);
}else{
printf("<led_on/led_off> <0/1/2/3>\n");
}
}
close(fd);
return 0;
}
Makefile
KERNEL=/home/cjl/driver/kernel/
obj-m += led_drv.o
all:
make -C $(KERNEL) M=$(PWD) modules
clean:
make -C $(KERNEL) M=$(PWD) clean