linux内核字符设备驱动之发送命令接口

1.linux内核字符设备驱动之发送命令(ioctl)接口


 int ioctl(int fd, int request, ...);

函数功能:

1.应用程序可以向设备发送控制命令;
2.应用程序还可以和设备进行读/写操作;

参数:

fd:设备文件描述符
request:向设备发送的命令字,命令定义;
#define LED_ON 0x100001 //开灯命令
#define LED_OFF 0x100002 //关灯命令
...: 可变参数, 如果传递第三个参数,第三个参数为用户缓冲区的首地址

例如:
用户需求:向设备发送开灯命令:
ioctl(fd, LED_ON);

用户需求:向设备发送开灯命令,并且写一个数据给设备;
int udata = 0x5555;

ioctl(fd, LED_ON, &udata);

对应的底层驱动的ioctl接口:

struct file_operations {
long (*unlocked_ioctl)(struct file *file,
unsigned int cmd,
unsigned long arg);
};

unlocked_ioctl接口作用:

1.处理用户的命令请求
2.完成和用户空间的数据交互(读或者写)
调用关系:
应用程序调用ioctl->....->驱动的unlocked_ioctl
参数:
file:文件指针
cmd:用户发送来的命令字
arg:如果用户传递三个参数,保存着用户发送来的用户缓冲区的首地址,内核不能直接访问操作,必须利用copy_to_user/copy_from_user完成数据的拷贝;
注意在使用的时候要进行数据类型的转换

案例:利用ioctl实现开关某个灯;
int uindex = 1;
ioctl(fd, LED_ON, &uindex);

int uindex = 2;
ioctl(fd, LED_ON, &uindex);

int uindex = 1;
ioctl(fd, LED_OFF, &uindex);

int uindex = 2;

ioctl(fd, LED_OFF, &uindex);、

底层驱动代码:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h> //struct file_operations
#include <linux/cdev.h> //struct cdev + 设备号
#include <asm/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/uaccess.h> //copy_to_user

//声明LED操作的数据结构
struct led_cmd {
    int index;
    int cmd;
};

//声明描述LED硬件相关的数据结构
struct led_resource {
    char *name;
    int gpio;
};

//定义初始化LED硬件信息
static struct led_resource led_info[] = {
    [0] = {
        .name = "LED1",
        .gpio = S5PV210_GPC0(3)
    },
    [1] = {
        .name = "LED2",
        .gpio = S5PV210_GPC0(4)
    }
};

//定义设备号
static dev_t dev;

//定义字符设备对象
static struct cdev led_cdev;

//调用关系:应用程序open->....->led_open
static int led_open(struct inode *inode, struct file *file)
{
    int i;
    
    for(i = 0; i < ARRAY_SIZE(led_info); i++)
        gpio_set_value(led_info[i].gpio, 1);
    
    printk("%s\n", __func__);
    return 0; //执行成功返回0,执行失败返回负值    
}

//调用关系:应用程序close->...->led_close
static int led_close(struct inode *inode, struct file *file)
{
    int i;
    
    for(i = 0; i < ARRAY_SIZE(led_info); i++)
        gpio_set_value(led_info[i].gpio, 0);
    
    printk("%s\n", __func__);
    return 0; //执行成功返回0,执行失败返回负值    
}

//调用关系:应用程序read->...->led_read
static ssize_t led_read(struct file *file,
                        char __user *buf,
                        size_t count,
                        loff_t *ppos)
{
    //定义初始化内核缓冲区(存储空间再后1G虚拟内存中)
    int kdata = 0x5555;

    //将内核数据上报给用户
    //切记:buf虽然保存的用户缓冲区的首地址,但不能直接访问
    //*(int *)buf = kdata;错误
    copy_to_user(buf, &kdata, sizeof(kdata));
    printk("%s\n", __func__);    
    return sizeof(kdata); //失败返回负值,成功返回实际读取的字节数
}

//调用关系:应用程序write->...->最终调用led_write
static ssize_t led_write(struct file *file,
                        const char __user *buf,
                        size_t count,
                        loff_t *ppos)
{
    //定义内核缓冲区
    struct led_cmd kdata;

    //拷贝用户数据到内核
    copy_from_user(&kdata, buf, sizeof(kdata));
    
    //开或者关灯
    gpio_set_value(led_info[kdata.index - 1].gpio, kdata.cmd);
    printk("%s:第%d灯被%s\n", 
            __func__, kdata.index, kdata.cmd?"打开":"关闭");
    return count; //失败返回负值,成功返回写入的字节数
}

#define LED_ON  0x100001 //开灯命令
#define LED_OFF 0x100002 //关灯命令

static long led_ioctl(struct file *file,
                        unsigned int cmd,
                        unsigned long arg)
{
    //定义内核缓冲区,保存用户缓冲区的数据
    int kindex;

    //拷贝用户数据到内核
    copy_from_user(&kindex, (int *)arg, sizeof(kindex));
    
    //解析处理用户命令
    switch(cmd) {
        case LED_ON:
            gpio_set_value(led_info[kindex-1].gpio, 1);
            printk("%s:第%d个灯被打开!\n", __func__, kindex);
            break;
        case LED_OFF:
            gpio_set_value(led_info[kindex-1].gpio, 0);
            printk("%s:第%d个灯被关闭!\n", __func__, kindex);
            break;
        default:
            return -1;
    }
    return 0; //成功返回0,失败返回-1
}

//定义初始化硬件操作方法
static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open, //打开设备
    .release = led_close, //关闭设备
    .read = led_read, //读取设备
    .write = led_write, //写设备
    .unlocked_ioctl = led_ioctl //向设备发送命令和完成读写
};

static int led_init(void)
{
    int i;
    //申请设备号
    alloc_chrdev_region(&dev, 0, 1, "tarena");
    
    //初始化字符设备对象
    cdev_init(&led_cdev, &led_fops);
    
    //注册字符设备对象到内核
    cdev_add(&led_cdev, dev, 1);
    
    //申请GPIO资源和配置GPIO为输出口,输出0(省电)
    for (i = 0; i < ARRAY_SIZE(led_info); i++) {
        gpio_request(led_info[i].gpio, led_info[i].name); 
        gpio_direction_output(led_info[i].gpio, 0);
    }
    return 0;
}

static void led_exit(void)
{
    int i;

    //输出0,释放GPIO资源
    for (i = 0; i < ARRAY_SIZE(led_info); i++) {
        gpio_set_value(led_info[i].gpio, 0);
        gpio_free(led_info[i].gpio);
    }`
    //卸载字符设备对象
    cdev_del(&led_cdev);
    //释放设备号
    unregister_chrdev_region(dev, 1);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");


应用层代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define LED_ON  0x100001
#define LED_OFF 0x100002

int main(void)
{
    int fd;
    int uindex; //用户缓冲区

    //打开设备
    //open->....->调用led_open
    fd = open("/dev/myled", O_RDWR);
    if (fd < 0) {
        printf("打开设备失败!\n");
        return -1;
    }
    
    //write->...->调用led_write
    while (1) {
        uindex = 1;
        ioctl(fd, LED_ON, &uindex);
        sleep(1);
        uindex = 2;
        ioctl(fd, LED_ON, &uindex);
        sleep(1);
        uindex = 1;
        ioctl(fd, LED_OFF, &uindex);
        sleep(1);
        uindex = 2;
        ioctl(fd, LED_OFF, &uindex);
        sleep(1);
    }
    //关闭设备
    //close->...->调用led_close
    close(fd);
    return 0;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章