Driver:LED灯操作、内核空间和用户空间的数据交互、ioctl函数、设备文件安装与销毁

来源:互联网 发布:学python可以做什么 编辑:程序博客网 时间:2024/05/20 17:10
1、LED灯操作
用户空间执行open时,led1亮;执行close时,led1灭。
电路原理图:
    led1 ---> GPIOC12
如何控制:
    cpu datasheet 特殊功能寄存器

在linux下操作GPIO管脚有两种方式:
    1)像裸板开发一样通过'指针 + 位操作'来完成特殊功能寄存器的设置。
        问题在于:/* 要将特殊功能寄存器的物理地址转换成虚拟地址。*/
    2)通过GPIO库函数的方式
        在内核中提供了操作GPIO管脚的库函数,其使用有固定的套路:
        a)申请管脚;
           intgpio_request(unsigned gpio, const char *label);
                功能:向内核申请GPIO资源
                gpio:GPIO软件编号
                name:标识
        b)使用管脚;
            intgpio_direction_output(int gpio, int value);
                功能:设置GPIO为输出口,并且输出value值
                gpio:GPIO软件编号
                value:管脚状态,1和0代表高低电平
            intgpio_direction_inputput(int gpio);
                功能:设置GPIO为输入口
                gpio:GPIO软件编号
            voidgpio_set_value(int gpio, int value);
                功能:设置GPIO的状态为value
                gpio:GPIO软件编号
                value:管脚状态
            intgpio_get_value(int gpio);
                功能:获取GPIO的状态,返回值为状态信息
                gpio:GPIO软件编号
                返回值:非0 - 高电平;0 - 低电平            
        c)释放管脚;
            voidgpio_free(unsigned gpio);
                功能:释放GPIO资源
                gpio:GPIO软件编号

<GPIO软件编号>
/*  gpio group pad start num. */enum {    PAD_GPIO_A      = (0 * 32),    PAD_GPIO_B      = (1 * 32),    PAD_GPIO_C      = (2 * 32), // GPIOC12 == PAD_GPIO_C + 12 == 64+12 == 76    PAD_GPIO_D      = (3 * 32),    PAD_GPIO_E      = (4 * 32),    PAD_GPIO_ALV    = (5 * 32),};
/** 代码演示 - led驱动 **/#include <linux/init.h>#include <linux/module.h>#include <linux/fs.h>#include <linux/cdev.h>#include <linux/gpio.h>#include <mach/platform.h>MODULE_LICENSE ("GPL");dev_t dev;                // 存储设备号unsigned int major = 0;   // 主设备号unsigned int minor = 100;   // 次设备号/* 1. 定义cdev类型的变量 */struct cdev led_cdev;int led_open (struct inode* inode, struct file* filp){    gpio_set_value (PAD_GPIO_C + 12, 0); // 低电平:亮    return 0;}int led_release (struct inode* inode, struct file* filp){    gpio_set_value (PAD_GPIO_C + 12, 1); // 高电平:灭    return 0;}struct file_operations led_fops = {    .owner   = THIS_MODULE,    .open    = led_open,    .release = led_release,};int __init char_drv_init (void){    if (major) {        /* 静态注册 */        dev = MKDEV (major, minor);        register_chrdev_region (dev, 1, "jiangyuan-1610");    }       else {        /* 动态注册 */        alloc_chrdev_region (&dev, minor, 1, "jiangyuan-alloc-1610");    }       /* 2. 初始化cdev */    cdev_init (&led_cdev, &led_fops);    /* 3. 注册cdev */    cdev_add (&led_cdev, dev, 1);     /* 申请GPIO管脚 */    gpio_request (PAD_GPIO_C + 12, "LED1"); // name自定    /* 配置管脚为输出模式 */    gpio_direction_output (PAD_GPIO_C + 12, 1); // 高电平:灭    return 0;}void __exit char_drv_exit (void){    /* 注销cdev */    cdev_del (&led_cdev);    /* 注销设备号 */    unregister_chrdev_region (dev, 1);     /* 释放GPIO管脚 */    gpio_free (PAD_GPIO_C + 12);}module_init (char_drv_init);module_exit (char_drv_exit);

验证:
    把内核自带的led驱动去掉,重新烧写内核到开发板,再验证新的led驱动。
    $:' make menuconfig
        Device Drivers  ---> 
        Character devices  ---> 
               < > My led driver  // 没有就不用管
            -*- LED Support  ---> 
               < >   LED Support for GPIO connected LEDs
               < >   PWM driven LED Support  
               [ ]   LED Trigger support       

    $:' make uImage -j4
    $:'cp arch/arm/boot/uImage /tftpboot

    #:'tftp 48000000 uImage
    #:'mmc write 48000000 800 3000
    #:'re
    #:'insmod char_drv.ko
    #:'mknod /dev/leds c 244 100
        // 主设备号要进行查看:cat /proc/devices
    #:'./test

2、内核空间和用户空间的数据交互
    用户空间不能直接操作内核空间地址中的数据;
    内核空间不能直接操作用户空间地址中的数据。

    write (fd, buf, len);
        buf:源数据区,用户空间地址(0~3G)
    
    xxx write(struct file *, const char __user *buf, size_t, loff_t *) {
        char tmp = *buf; // 不允许的操作。
    }
    
    有需求时,可以'通过以下函数,将用户空间数据拷贝到 → 内核空间':
    'copy_from_user'
    intcopy_from_user(void *to, const void __user *from, int n);
    功能:用户空间 → 内核空间
    参数:
        @to:数据拷到哪里去
        @from:数据从哪里拷
        @n:字节数
    返回值: 成功 - 0 ,失败 - 未拷贝成功的字节数。

    有需求时,可以'通过以下函数,将内核空间数据拷贝到 → 用户空间':
    'copy_to_user'
    intcopy_to_user(void *to, const void __user *from, int n);
    功能:内核空间 → 用户空间
    参数:
        @to:数据拷到哪里去
        @from:数据从哪里拷
        @n:字节数
    返回值: 成功 - 0 ,失败 - 未拷贝成功的字节数。

/** 代码演示 - char_dev.c **/#include <linux/init.h>#include <linux/module.h>#include <linux/fs.h>#include <linux/cdev.h>#include <linux/gpio.h>#include <mach/platform.h>#include <linux/uaccess.h>MODULE_LICENSE ("GPL");dev_t dev;                // 存储设备号unsigned int major = 0;   // 主设备号unsigned int minor = 100;   // 次设备号int led_status = 0;/* 1. 定义cdev类型的变量 */struct cdev led_cdev;int led_open (struct inode* inode, struct file* filp){    return 0;}int led_release (struct inode* inode, struct file* filp){    return 0;}/* write (fd, buf, len) */ssize_t led_write (struct file* filp, const char __user* buf,                   size_t len, loff_t* offset) {    int cmd = 0;    int ret = 0;    /* 拷贝用户空间的值,到内核空间 */    ret = copy_from_user (&cmd, buf, len);    printk ("write - ret = %d\n", ret);    gpio_set_value (PAD_GPIO_C + 12, cmd);    led_status = cmd; // 记录灯的状态    return len;}/* read (fd, buf, len); */ssize_t led_read (struct file* filp, char __user* buf,                  size_t len, loff_t* offset) {    int ret = 0;    ret = copy_to_user (buf, &led_status, len);    return len;}struct file_operations led_fops = {    .owner   = THIS_MODULE,    .open    = led_open,    .release = led_release,    .write   = led_write,    .read    = led_read,};int __init char_drv_init (void){    if (major) {        /* 静态注册 */        dev = MKDEV (major, minor);        register_chrdev_region (dev, 1, "jiangyuan-1610");    }       else {        /* 动态注册 */        alloc_chrdev_region (&dev, minor, 1, "jiangyuan-alloc-1610");    }       /* 2. 初始化cdev */    cdev_init (&led_cdev, &led_fops);    /* 3. 注册cdev */    cdev_add (&led_cdev, dev, 1);     /* 申请GPIO管脚 */    gpio_request (PAD_GPIO_C + 12, "LED1"); // name自定    gpio_request (PAD_GPIO_C + 11, "LED2"); // name自定    gpio_request (PAD_GPIO_C + 7, "LED3");  // name自定    gpio_request (PAD_GPIO_B + 26, "LED4"); // name自定    /* 配置管脚为输出模式 */    gpio_direction_output (PAD_GPIO_C + 12, 1); // 高电平:灭    gpio_direction_output (PAD_GPIO_C + 11, 1); // 高电平:灭    gpio_direction_output (PAD_GPIO_C + 7, 1);  // 高电平:灭    gpio_direction_output (PAD_GPIO_B + 26, 1); // 高电平:灭    return 0;}void __exit char_drv_exit (void){    /* 注销cdev */    cdev_del (&led_cdev);    /* 注销设备号 */    unregister_chrdev_region (dev, 1);     /* 释放GPIO管脚 */    gpio_free (PAD_GPIO_C + 12);    gpio_free (PAD_GPIO_C + 11);    gpio_free (PAD_GPIO_C + 7);     gpio_free (PAD_GPIO_B + 26);}module_init (char_drv_init);module_exit (char_drv_exit);/** 测试代码 - test.c **/#include <stdio.h>#include <fcntl.h>/* ./test on/off */int main (int argc, char* argv[]){    int fd = 0;    int cmd = 0;    int status = 0;    int res = 0;    if (argc != 2){         printf ("usage: %s <on/off>\n", argv[0]);        return -1;     }       fd = open ("/dev/leds", O_RDWR); // 注意此处的权限问题    if (fd < 0) {        perror ("open /dev/leds failed...\n");        return -1;     }       printf ("open /dev/leds successed...\n");    if (! strcmp (argv[1], "on")) {        cmd = 0;        res = write (fd, &cmd, sizeof (int)); // 亮灯        if (-1 == res) {            perror ("write failed:\n");            return 0;        }       }       else if (! strcmp (argv[1], "off")) {        cmd = 1;        res = write (fd, &cmd, sizeof (int)); // 灭灯        if (-1 == res) {            perror ("write failed:\n");            return 0;        }       }       read (fd, &status, sizeof (int));    if (status == 0) {        printf ("now led1~4 are on...\n");    }       else {        printf ("now led1~4 are off...\n");    }       sleep (1);    printf ("now is closing device!\n");    sleep (3);    close (fd);    return 0;}

    还有一套函数,可以实现 用户空间 ←→ 内核空间:
        'get_user'
        intget_user(data, ptr);
            data: 可以是字节、半字、字、双字类型的内核变量
            ptr: 用户空间内存指针
            返回: 成功返回0,失败返回非0
        'put_user'
        unsigned longput_user(data, ptr);
            data: 可以是字节、半字、字、双字类型的内核变量
            ptr: 用户空间内存指针
            返回: 成功返回0,失败返回非0

3、设备控制操作函数 ioctl

    'ioctl:设置/获取设备的属性'

    uart驱动程序:
        uart_puts ---> write
        uart_gets ---> read

    如果想把uart控制器的波特率调整为9600

    用户空间函数:
       #include <sys/ioctl.h>
       intioctl(int d, int request, ...);

    内核中在file_operations中设计了此函数:
    'unlocked_ioctl'
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    功能:内核中控制设备属性
    参数:
        @inode: 待操作的设备文件inode结构体指针
        @filp: 待操作的设备文件file结构体指针
        @cmd: 接收到的设备控制命令
        @arg: 控制命令可能携带的参数
    返回值: 成功返回0,失败返回负值;如果access_ok失败,ioctl操作应该返回-EFAULT
    // -EFAULT / -EINVAL 内核中的错误信息宏。

    用户空间:
        ioctl (fd, cmd, val);
        ioctl (fd, cmd);
-----------------------------------
    内核空间:
        unlocked_ioctl ();

4、设备文件的安装与销毁
    模块安装成功,自动创建对应的设备文件;
    模块卸载成功,自动销毁对应的设备文件。

    需要的步骤:
        1)通过busybox中生成的命令中要确认有 mdev
            #:'which mdev
        2)etc/inid.d/rcS
            mount -a
        3)etc/fstab
            proc    /proc   proc    defaults    0   0
            sysfs   /sys    sysfs   defaults    0   0

        4)etc/inid.d/rcS  // 规定了产生热插拔事件执行哪个命令
            #热插拔事件处理 创建设备文件
            echo /sbin/mdev >/proc/sys/kernel/hotplug

            热插拔事件:
                1)U盘的插入和拔出;
                2)/sys/目录下文件的变化。
            mdev会根据/sys/目录下文件的变化自动创建设备文件。
        5)让/sys/产生变化
        'class_create'
        #defineclass_create(owner, name);
        功能:创建树形结构中的一个树枝(设备文件)
        参数:
            @owner:THIS_MODULE
            @name:类的名称
        eg: struct class *mycls = class_create(THIS_MODULE,“tarena”);

        'device_create'
        struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);
        功能:创建树形结构上的果实(设备节点)
        参数:
            @class:该设备属于哪个类,该果实结在哪个树枝上
            @parent:父设备
            @devt:设备号
            @drvdata:创建设备时传递的参数,通常给NULL
            @*fmt:...
                    创建的果实的名称,将来mdev帮助自动创建的设备文件的名称
                    "led%d", i
                    假如i=0   "led0"
                    "led%s", str
                    假如str="aaa"  "ledaaa"
        eg: device_create(mycls, NULL, MKDEV(major, minor), NULL,"myled");

        'device_destroy'
        voiddevice_destroy(struct class *class, dev_t devt);

        'class_destroy'
        voidclass_destroy(struct class *cls);

       // 逆序销毁设备文件。

/** 代码演示 - char_drv.c **/#include <linux/init.h>#include <linux/module.h>#include <linux/fs.h>#include <linux/cdev.h>#include <linux/gpio.h>#include <mach/platform.h>#include <linux/uaccess.h>#include <linux/device.h>MODULE_LICENSE ("GPL");#define CMD_LED_ON  0x10001#define CMD_LED_OFF 0x10002dev_t dev; // 存储设备号unsigned int major = 0;   // 主设备号unsigned int minor = 100;   // 次设备号int led_status = 0;struct class *cls = NULL; // 保存class_create的返回值/* 1. 定义cdev类型的变量 */struct cdev led_cdev;int led_open (struct inode* inode, struct file* filp){    return 0;}int led_release (struct inode* inode, struct file* filp){    return 0;}/* write (fd, buf, len) */ssize_t led_write (struct file* filp, const char __user* buf, size_t len, loff_t* offset) {    return 0;}/* read (fd, buf, len); */ssize_t led_read (struct file* filp, char __user* buf, size_t len, loff_t* offset) {    return 0;}/* ioctl (fd, cmd, arg) */long led_ioctl (struct file* filp, unsigned int cmd, unsigned long arg) {    int ret = 0;    int index = 0;    ret = copy_from_user (&index, (void*)arg, 4);    switch (cmd) {    case CMD_LED_ON:        if (index == 1)            gpio_set_value (PAD_GPIO_C + 12, 0);        else if (index == 2)            gpio_set_value (PAD_GPIO_C + 7, 0);            break;    case CMD_LED_OFF:        if (index == 1)            gpio_set_value (PAD_GPIO_C + 12, 1);        else if (index == 2)            gpio_set_value (PAD_GPIO_C + 7, 1);            break;        default:            return -EINVAL;    }    return 0;}struct file_operations led_fops = {    .owner    = THIS_MODULE,    .open    = led_open,    .release        = led_release,    .write          = led_write,    .read    = led_read,    .unlocked_ioctl = led_ioctl,};int __init char_drv_init (void){    if (major) {        /* 静态注册 */        dev = MKDEV (major, minor);        register_chrdev_region (dev, 1, "jiangyuan-1610");    }    else {        /* 动态注册 */        alloc_chrdev_region (&dev, minor, 1, "jiangyuan-alloc-1610");    }    /* 2. 初始化cdev */    cdev_init (&led_cdev, &led_fops);    /* 3. 注册cdev */    cdev_add (&led_cdev, dev, 1);    /* 影响/sys/class/目录 */    cls = class_create (THIS_MODULE, "myleds"); // 创建树枝    /* 影响/sys/class/myleds/目录 */    device_create (cls, NULL, dev, NULL, "leds"); // 创建果实    /* 申请GPIO管脚 */    gpio_request (PAD_GPIO_C + 12, "LED1"); // name自定    gpio_request (PAD_GPIO_C + 7, "LED3");  // name自定    /* 配置管脚为输出模式 */    gpio_direction_output (PAD_GPIO_C + 12, 1); // 高电平:灭    gpio_direction_output (PAD_GPIO_C + 7, 1);  // 高电平:灭    return 0;}void __exit char_drv_exit (void){    /* 注销cdev */    cdev_del (&led_cdev);    /* 注销设备号 */    unregister_chrdev_region (dev, 1);    /* 释放GPIO管脚 */    gpio_free (PAD_GPIO_C + 12);    gpio_free (PAD_GPIO_C + 7);    /* 销毁果实 */    device_destroy (cls, dev);    /* 销毁树枝 */    class_destroy (cls);}module_init (char_drv_init);module_exit (char_drv_exit);

练习:
    ./test <1/2/3/4> <on/off>
0 0
原创粉丝点击