您的位置:首页 > 其它

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

2017-03-04 21:33 459 查看
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 0x10002

dev_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>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  arm 驱动
相关文章推荐