linux驱动开发LDD(五)——字符设备驱动编程、GPIO库函数、综合运用goto语句、ioctl

基于上一节的字符驱动编程框架,控制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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值