地址映射

        MMU( MemoryManage Unit):内存管理单元

             完成虚拟空间到物理空间的映射
             内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性

        地址映射:虚拟空间到物理空间的映射

         虚拟地址(VA ,Virtual Address)

         物理地址(PA ,Physcical Address)

      32 位的处理器,虚拟地址范围: 2 ^ 32=4GB

  内存映射:

     Linux 内核启动时会初始化 MMU,设置好内存映射,设置后 CPU 访问的都是 虚拟地 址


     ioremap 函数

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

// linux-5.5.4/arch/arc/mm/ioremap.c
/*
 * paddr: 物理起始地址
 * size: 内存空间大小
 * return : 虚拟空间首地址
 */
void __iomem *ioremap(phys_addr_t paddr, unsigned long size)
{
	phys_addr_t end;

	/* Don't allow wraparound or zero size */
	end = paddr + size - 1;
	if (!size || (end < paddr))
		return NULL;

	/*
	 * If the region is h/w uncached, MMU mapping can be elided as optim
	 * The cast to u32 is fine as this region can only be inside 4GB
	 */
	if (arc_uncached_addr_space(paddr))
		return (void __iomem *)(u32)paddr;

	return ioremap_prot(paddr, size, PAGE_KERNEL_NO_CACHE);
}
EXPORT_SYMBOL(ioremap);

iounmap 函数

      释放掉 ioremap 函数所做的映射

// linux-5.5.4/arch/arc/mm/ioremap.c
/*
 * addr: 取消映射的虚拟地址空间首地址
 */
void iounmap(const void __iomem *addr)
{
	/* weird double cast to handle phys_addr_t > 32 bits */
	if (arc_uncached_addr_space((phys_addr_t)(u32)addr))
		return;

	vfree((void *)(PAGE_MASK & (unsigned long __force)addr));
}
EXPORT_SYMBOL(iounmap);

  I/O 内存访问函数

        I/O 端口 : 外部寄存器或内存映射到 IO 空间

        I/O 内存 :外部寄存器或内存映射到内存空间


       读操作函数

// linux-5.5.4/arch/alpha/kernel/io.c

/*
 * addr : 读取内存地址
 * return : 数据
 */

/* 8bit 读操作 */
u8 readb(const volatile void __iomem *addr)
{
	u8 ret = __raw_readb(addr);
	mb();
	return ret;
}
/* 16bit 读操作 */
u16 readw(const volatile void __iomem *addr)
{
	u16 ret = __raw_readw(addr);
	mb();
	return ret;
}
/* 32bit 读操作 */
u32 readl(const volatile void __iomem *addr)
{
	u32 ret = __raw_readl(addr);
	mb();
	return ret;
}
/* 64bit 读操作 */
u64 readq(const volatile void __iomem *addr)
{
	u64 ret = __raw_readq(addr);
	mb();
	return ret;
}

       写操作函数    

//linux-5.5.4/arch/alpha/kernel/io.c
/*
 * b : 写入的数值
 * addr : 写入的地址
 */
/* 8bit写操作 */
void writeb(u8 b, volatile void __iomem *addr)
{
	mb();
	__raw_writeb(b, addr);
}
/* 16bit 写操作 */
void writew(u16 b, volatile void __iomem *addr)
{
	mb();
	__raw_writew(b, addr);
}
/* 32bit 写操作 */
void writel(u32 b, volatile void __iomem *addr)
{
	mb();
	__raw_writel(b, addr);
}
/* 64bit 写操作 */
void writeq(u64 b, volatile void __iomem *addr)
{
	mb();
	__raw_writeq(b, addr);
}

    硬件电路:

    手册:

          配置 GPIO1 时钟

 

          配置 GPIO1_IO03 的复用功能
 

       配置 GPIO1_IO03

         

 

 

   led 的驱动程序 :

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define    LED_MAJOR   140      // 主设备号
#define    LED_NAME    "led"    // 设备名
#define    LEDOFF      0        // 关灯
#define    LEDON       1        // 开灯

/* 寄存器物理地址 */
#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;

/*
 * 控制LED
 * sta : LEDON 开 , LEDOFF 关
 */
void led_switch(u8 sta)
{
    u32 val = 0;

    if(sta == LEDON)
    {
        val = readl(GPIO1_DR);
        val &= ~(1 << 3);
    }
    else if(sta == LEDOFF)
    {
        val = readl(GPIO1_DR);
        val |= (1 << 3);
        writel(val, GPIO1_DR);
    }
}

/*
 * 打开设备
 * inode:传递驱动inode
 * filp: 设备文件
 * return:成功: 0 失败:其他
 */
static int led_open(struct inode *inode, struct file *filp)
{
    printk(" led_open\r\n");
    return 0;
}

/*
 * 从设备读数据
 * filp:打开的设备文件(文件描述符)
 * buf:返回给用户空间的数据缓冲区
 * cnt:读取的数据长度
 * offt:相对于文件首地址的偏移
 * return:成功:字节数,失败:负值
 */
static ssize_t led_read(struct file *filp,
                        char __user *buf,
                        size_t cnt, 
                        loff_t *offt)
{
    printf("led_read \r\n");
    return 0;
}

/*
 * 向设备写数据
 * filp:设备文件
 * buf:向设备写入的数据
 * cnt:写入数据长度
 * offt:相对于文件首地址的偏移
 * return:成功:写入字节数 失败:负值
 */
static ssize_t led_write(struct file *filp, 
                         const char __user *buf, 
                         size_t cnt, 
                         loff_t *offt)
{
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;
    
    retvalue = copy_from_user(databuf, buf, cnt);

    if(retvalue < 0)
    {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }
    
    ledstat = databuf[0];    // 状态值
    
    if(ledstat == LEDON)
    {
        led_switch(LEDON);    // 开灯
    }
    else if(ledsstat == LEDOFF)
    {
        led_switch(LEDOFF);    //关灯
    }

    return 0;
}

/*
 * 关设备
 * inode: 驱动的 inode
 * filp:要关的设备文件
 */
static int led_release(struct inode *inode, struct file *filp)
{
    printk("led_release\r\r");
    return 0;
}

/* 设备操作数 */
static struct file_operations led_fogs =
{
    .owner = THIS_MODULE,
    .open = led_open,
    .read = led_read,
    .write = led_write,
    release = led_release,
};

/*
 * 驱动入口
 * return:成功 0
 */
static int __init led_init(void)
{
    int retvalue = 0;
    u32 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);
    
    // 使能GPIO1时钟
    val = readl(IMX6U_CCM_CCGR1);
    val &= ~(3 << 26);    // 清除以前的设置
    val |= (3 << 26);    // 设置新值
    writel(val, IMX6U_CCM_CCGR1);

    // 复用GPIO1_3
    val = readl(SW_MUX_GPIO1_IO03);
    val &= ~(F << 0);    // 清除以前的设置
    val |= (5 << 0);    // 设置新值
    writel(val,SW_MUX_GPIO1_IO03);
    
    //
    /*
     * 16bit:0 禁止施密特
     * 15 14bit:00 设置100k下拉
     * 13:0 状态保存器
     * 12:1 pull/keeper使能
     * 11:0 关闭开路输出
     * 10 8:000 保留
     * 7 6:10 max(200MHZ)
     * 5 3:110 R0/6
     * 2 1:00 保留
     * 0:0 低压摆率
     */
    val = readl(SW_PAD_GPIO1_IO03);
    val &= ~(1FFFF << 0);    // 清除以前的设置
    val |= (10B0 << 0);    // 设置新值
    writel(val,SW_PAD_GPIO1_IO03);

    // 注册设备
    retvalue = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);

    if(retvalue < 0)
    {
        printk("register chrdev failed \r\n");
        return -EIO;
    }

    return 0;
}

static void __exit led_exit(void)
{
    // 取消映射
    iounmem(IMX6U_CCM_CCGR1);
    iounmem(SW_MUX_GPIO1_IO03);
    iounmem(SW_PAD_GPIO1_IO03);
    iounmem(GPIO1_DR);
    iounmem(GPIO1_GDIR);

    // 注销设备
    unregister_chrdev(LED_MAJOR, LED_NAME);
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("CpuCode");

应用程序:

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

#define    LEDOFF    0
#define    LEDON     1

int main(int argc, char *argv[])
{
    int fd;
    int retvalue;
    char *filename;
    unsigned char databuf[1];

    if(argc != 3)
    {
        printf("Error Usage \r\n");
        return -1;
    }

    filename = argv[1];
    fd = open(filename, O_RDWR);
    
    if(fd < 0)
    {
        printf("file %s open failed \r\n", argv[1]);
        return -1;
    }

    databuf[0] = atoi(argv[2]);
    //向文件写数据
    retvalue = write(fd, databuf, sizeof(databuf));
    
    if(retvalue < 0)
    {
        printf("LED Control Failed \r\n");
        close(fd);
        return -1;
    }
    //关闭文件
    retvalue = close(fd);
    
    if(retvalue < 0)
    {
        printf("file %s close failed \r\n", argv[1]);
        return -1;
    }
    return 0;
}


Makefile:

KERNELDIR := /home/cpucode/linux-5.5.7
CURRENT_PATH := $(shell pwd)
obj-m := led.o

build := kernel_modules

kernel_modules:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

clean:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean