从零开始之驱动发开、linux驱动(四、字符驱动之led驱动简单使用)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_16777851/article/details/82226543

本节在上一节的驱动程序中添加基本硬件操作函数。

这一节我们开始增加一个led灯的驱动程序。

点亮一个led灯只需要两个寄存器,即配置io口的控制寄存器和状态寄存器。

基础知识:

在linux系统中,开启mmu后,我们不能直接使用寄存器的硬件地址(或者说我们不知道,寄存器硬件地址被映射到那块内存了),所以我们只能使用虚拟地址来操纵寄存器。而目前我们不知道虚拟地址,只知道物理地址。

所以系统给我们提供了一个接口函数,用来通过寄存器的物理地址得到寄存器的虚拟地址。

void __iomem *ioremap(phys_addr_t offset, unsigned long size)

它的第一个输入参数是物理地址,第二个输入参数是请求的寄存器的长度。输出参数是虚拟地址。

其功能是建立新的一个页表,把输入的物理地址映射到内核空间的一块虚拟地址。(记住是映射一页)

所以使用完一定要释放掉,否则块内核的内存就不能再被使用了。

static inline void iounmap(void __iomem *addr)

取消映射,使用的是要释放的虚拟地址。

/* 内核空间和用户空间使用的不同的地址,所以它们之间的指针里面的内容使用都需要转换 */
static inline int copy_from_user(void *to, const void __user volatile *from,
				 unsigned long n)    /* 从用户空间向内核空间传参  */
这里的from是用户空间的地址
/* 内核空间和用户空间使用的不同的地址,所以它们之间的指针里面的内容使用都需要转换 */
static inline int copy_to_user(void __user volatile *to, const void *from,
			       unsigned long n)    /* 从内核空间向用户空间传参  */
这里的to是用户空间的地址

注:上面用到的四个函数,后面我会分析。

我们先操纵一个GPH0_3端口

控制寄存器和数据寄存器的地址相连,所以我们申请8个字节。

控制寄存器的12-15bit初始化为1

下面就是在上一节的框架基础上填充了,io寄存器申请,操作。


#include <linux/fs.h>       /* 包含file_operation结构体 */
#include <linux/init.h>     /* 包含module_init module_exit */
#include <linux/module.h>   /* 包含LICENSE的宏 */
#include <asm/uaccess.h>    /* 包含copy_to_user之类 */
#include <linux/io.h>       /* 包含ioremap之类 */
   

/* GPJ0CON寄存器的物理地址 */
#define GPJ0CON_PA  0xe0200240

struct gpj0 {
    unsigned int gpj0con;
    unsigned int gpj0dat;
};

static struct gpj0* p_gpj0 = NULL;



/* 定义一个打开设备的,open函数 */
static int first_drv_open(struct inode *inodep, struct file *filep)
{
    printk("first_drv_open\n");
    
    /* 初始化gpj0_3为输出 */
    p_gpj0->gpj0con &= ~(0xf << 12);
    p_gpj0->gpj0con |= (1 << 12);
    
    /* 默认熄灭led灯 */
    p_gpj0->gpj0dat |= (1<<3);


    return 0;
}
 
/* 定义一个打开设备的,write函数 */
static ssize_t first_drv_write(struct file *filep, const char __user * buf, size_t len, loff_t *ppos)
{
    char buf[10];
    int ret;

    printk("first_drv_write\n");

    memset(buf, 0,10);
    ret = copy_from_user(buf, from, 1);    /* 内核空间和用户空间使用的不同的地址,所以它们之间的指针参数都需要转换 */
    if(ret)        /* 错误校验 */
    {
        printk("copy_from_user fail\n");    
    }

    if('0' == buf[0])
    {
        p_gpj0->gpj0dat |= (1<<3);        /* 灭灯 */
    }
    else if('1' == buf[0])
    {
        p_gpj0->gpj0dat &= ~(1<<3);       /* 亮灯 */
    }
    else
    {
        printk("please input 1/0 ?");     /* 使用提示 */
    }


}
 
/* 把自己定义的函数接口使用file_operations结构体封装起来,方便管理和使用 */
static const struct file_operations first_drv_file_operation = { 
    .owner = THIS_MODULE,
    .open  = first_drv_open,
    .write = first_drv_write, 
};
 
/* 注册驱动打包好的驱动程序 */
static int __init first_drv_init(void)
{
    printk("first_drv_init\n");
    register_chrdev(111,"first_drv",&first_drv_file_operation);
    
    /* 映射io寄存器 */
    p_gpj0 =  ioremap(GPJ0CON_PA, 0x08);
    if(NULL == p_gpj0)
    {   
        printk("ioremap fail\n");
        return -1; 
    }   
    return 0;
}
 
/* 卸载打包好的驱动程序 */
static void __exit first_drv_exit(void)
{
    printk("first_drv_exit\n");
    unregister_chrdev(111,"first_drv_init");
    /* 使用完取消映射 */
    iounmap(p_gpj0);

}
 
/* 声明函数属性 */
module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");

接下来看一下应用程序

应用程序很简单,和上一节没什么区别

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



int main(void)
{
    char buf[10] = "1";
    int fd = open("/dev/xxx", O_RDWR);

    if(fd < 0)
    {   
        printf("open /dev/xxx fail\n");
        return -1; 
    }   
    
    while('q' != buf[0])    /* 退出 */
    {           
        scanf("%s",buf);    /* 读参数 */
        write(fd, buf, 5); 
        printf("\n");

    }   
    return 0;
}

调试过程就可以看到led等的变化。

驱动程序不好的地方?

1.字符设备号需要自己先查看内核的/proc/devices里面没被使用的设备号,然后才能写代码,非常不具有可移植性

2.每次安装驱动后都需要手动创建设备节点

下一节,我们就来解决上面的两个问题

猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/82226543
今日推荐