linux驱动开发-2_点灯

本文介绍了如何在Linux下实现一个简单的LED驱动程序,包括驱动注册与卸载、设备文件操作、地址映射以及使用ioremap函数将物理地址转化为虚拟地址进行GPIO控制。作者详细展示了`led.c`文件中的关键函数和内存管理的相关概念。
摘要由CSDN通过智能技术生成

个人笔记向

上一次完成了一个驱动程序的框架,这次我来实现点灯。

首先呢,创建一个led.c文件用来初始化驱动程序。

创建好后第一件事就是驱动的注册与卸载,以及个人信息标注和添加模块 LICENSE 信息。

//注册与卸载
module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("lakzhu");

紧接着的就是 led_init、led_exit 这两个函数的编写了:

#define LED_MAJOR       200
#define LED_NAME        "led"

const const struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .write = led_write,
    .open = led_open,
    .release = led_release,
};


static int __init led_init(void)
{
    ret = register_chrdev(LED_MAJOR,LED_NAME,&led_fops);
    if(ret < 0){
        printk("register error\r\n");
        return -EIO;
    }
    printk("init\r\n");
    return 0;
}

static void __exit led_exit(void)
{
    unregister_chrdev(LED_MAJOR,LED_NAME);
    printk("exit\r\n");
}

在 led_init 里面要进行 led 驱动的设备号、设备名和设备结构体构建,使用 register_chrdev() 函数来注册,在这个 led_fops 设备结构体中写好调用的函数。.owner 一般就设置成 THIS_MODULE;write 函数用于向设备文件写入(发送)数据;open 函数用于打开设备文件;release 函数用于释放(关闭)设备文件,与应用程序中的 close 函数对应;

在 led_exit 里面要把申请过的设备号资源等进行卸载,使用 unregister_chrdev() 函数即可。

接着写好 led_fops 结构体中的函数:

static int led_open(struct inode *inode, struct file *fp)
{    
    printk("led_open\r\n");
    return 0;
}

static int led_release(struct inode *inode, struct file *fp)
{
    printk("led_release\r\n");
    return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf,
			 size_t count, loff_t *ppos)
{
    unsigned int retval;
    unsigned char databuff[1];
    retval = copy_from_user(databuff, buf, count);
    if(retval < 0){
        printk("write error");
        return -EFAULT;
    }

    switch_led(databuff[0]);
    return 0;
}

led_write 函数尤为重要,使用 copy_from_user函数传到驱动里面的数据复制到驱动里面。第一个值是存储数据的变量,第二个是需要传入的数据,第三个是传输总数。然后根据写入的数据来开关led。

led 的使用,我们这里使用寄存器来控制 led 的亮灭。

#define CCM_CCGR1_BASE          (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE  (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE  (0X020E02F4)
#define GPIO1_DR_BASE           (0X0209C000)
#define GPIO1_GDIR_BASE         (0X0209C004)
//地址映射后的指针
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

#define LEDOFF  0
#define LEDON   1

void switch_led(unsigned char temp){
    unsigned char val = 0;
    if(temp == 1){
        val = readl(GPIO1_DR);
        val &= ~(1 << 3);
        writel(val,GPIO1_DR);
    }
    else{
        val = readl(GPIO1_DR);
        val |= (1 << 3);
        writel(val,GPIO1_DR);
    }
}

static int __init led_init(void)
{
    int ret = 0;
    unsigned int val = 0;
    //初始化 地址映射 获取虚拟地址
    IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE,4);
    SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE,4);
    SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE,4);
    GPIO1_DR = ioremap(GPIO1_DR_BASE,4);
    GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE,4);

    //时钟初始化
    val = readl(IMX6U_CCM_CCGR1);
    val &= ~(3 << 26);
    val |= 3 << 26;
    writel(val,IMX6U_CCM_CCGR1);

    writel(0x5,SW_MUX_GPIO1_IO03);
    writel(0X10B0,SW_PAD_GPIO1_IO03);

    val = readl(GPIO1_GDIR);
    val |= 1 << 3;
    writel(val,GPIO1_GDIR);

    val = readl(GPIO1_DR);
    val |= (1 << 3);
    writel(val,GPIO1_DR);

    //注册字符设备
    ret = register_chrdev(LED_MAJOR,LED_NAME,&led_fops);
    if(ret < 0){
        printk("register error\r\n");
        return -EIO;
    }
    printk("init\r\n");
    return 0;
}

static void __exit led_exit(void)
{
 //   unsigned int val = 0;
 //   val = readl(GPIO1_DR);
 //   val |= (1 << 3);
 //   writel(val,GPIO1_DR);
    //取消地址映射
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO03);
    iounmap(SW_PAD_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);

    unregister_chrdev(LED_MAJOR,LED_NAME);
    printk("exit\r\n");
}

地址映射
我们先简单了解一下 MMU 这个神器,MMU 全称叫做 Memory 
Manage Unit,也就是内存管理单元。在老版本的 Linux 中要求处理器必须有 MMU,但是现在
Linux 内核已经支持无 MMU 的处理器了。MMU 主要完成的功能如下:
①、完成虚拟空间到物理空间的映射。
②、内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。
我们重点来看一下第①点,也就是虚拟空间到物理空间的映射,也叫做地址映射。首先了
解两个地址概念:虚拟地址(VA,Virtual Address)、物理地址(PA,PhyscicalAddress)。对于 32 位
的处理器来说,虚拟地址范围是 2^32=4GB,我们的开发板上有 512MB 的 DDR3,这 512MB 的
内存就是物理内存,经过 MMU 可以将其映射到整个 4GB 的虚拟空间。

Linux 内核启动的时候会初始化 MMU,设置好内存映射,设置好以后 CPU 访问的都是虚
拟地址。比如 I.MX6ULL 的 GPIO1_IO03 引脚的复用寄存器
IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 的地址为 0X020E0068。如果没有开启 MMU 的话
直接向 0X020E0068 这个寄存器地址写入数据就可以配置 GPIO1_IO03 的复用功能。现在开启
了 MMU,并且设置了内存映射,因此就不能直接向 0X020E0068 这个地址写入数据了。我们必
须得到 0X020E0068 这个物理地址在 Linux 系统里面对应的虚拟地址,这里就涉及到了物理内
存和虚拟内存之间的转换,需要用到两个函数:ioremap 和 iounmap

ioremap 函数用于获取指 定 物 理 地 址 空 间 对 应 的 虚 拟 地 址 空 间

#define ioremap(cookie,size) __arm_ioremap((cookie), (size), 
MT_DEVICE)

void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size, 
unsigned int mtype)
{
return arch_ioremap_caller(phys_addr, size, mtype,
__builtin_return_address(0));
}

ioremap 是个宏,有两个参数:cookie 和 size,真正起作用的是函数__arm_ioremap,此函
数有三个参数和一个返回值,这些参数和返回值的含义如下:
phys_addr:要映射的物理起始地址。
size:要映射的内存空间大小。
mtype:ioremap 的类型,可以选择 MT_DEVICE、MT_DEVICE_NONSHARED、
MT_DEVICE_CACHED 和 MT_DEVICE_WC,ioremap 函数选择 MT_DEVICE。
返回值:__iomem 类型的指针,指向映射后的虚拟空间首地址

iounmap 函数
卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射,iounmap 函数原
型如下

void iounmap (volatile void __iomem *addr)

iounmap 只有一个参数 addr,此参数就是要取消映射的虚拟地址空间首地址.

通过 ioremap 函数把 led 寄存器的物理地址就能自动转化为虚拟地址返回。这样就能够使用 readl 和 writel 函数来控制 led 了。

ledApp 函数编写:

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

#define LEDOFF  0
#define LEDON   1

int main(int argc,char *argv[])
{
    int fd;
    int ret;
    char *filename;
    unsigned char databuff[1];
    if(argc != 3){
        printf("data deficiency\r\n");
    }
    filename = argv[1];

    fd = open(filename, O_RDWR);
    if(fd < 0){
        printf("file open errer\r\n");
        return -1;
    }
    databuff[0] = atoi(argv[2]);

    ret = write(fd,databuff,sizeof(databuff));
    if(ret < 0){
        printf("control failed\r\n");
        close(fd);
        return -1;
    }
    close(fd);
    return 0;
}

  • 14
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值