[TZ]内存与IO访问(4)-IO内存静态映射

转载请注明原文地址:http://blog.csdn.net/ts_dchs/article/details/50246543

1 流程分析

(本节以s5pv210(ARM A8) of Linux 3.0.26为例)
Linux提供外设IO内存物理地址到Linux虚拟地址的静态映射。静态映射是指通过map_desc结构体静态创建I/O资源映射表。
struct map_desc[code]结构体包含虚拟地址,设备物理地址。常用于寄存器资源映射,这样静态映射之后在编写内核代码或驱动时就不需要再ioremap。系统启动时创建好静态映射,程序直接通过映射后的虚拟地址去访问它们。

先说启动时做初始化的工作。内核提供了一个重要的结构体struct machine_desc[code] ,内核通过machine_desc结构体来控制系统体系架构相关部分的初始化。为了初始化工作,包括map_io, init_irq, init_machine以及phys_io , timer成员等。

这里的map_io成员即内核提供给用户的创建外设I/O资源到内核虚拟地址静态映射表的接口函数。map_io成员函数会在系统初始化过程中Start_kernel -> setup_arch() –> paging_init() –> devicemaps_init()中被调用。结构体通过MACHINE_START[code]宏初始化。这部分根据平台不同设计。

//LinuxSrc/arch/arm/mach-s5pv210/mach-smdkv210.c
MACHINE_START(SMDKV210, "SMDKV210")
    /* Maintainer: Kukjin Kim <kgene.kim@samsung.com> */
    .boot_params    = S5P_PA_SDRAM + 0x100,
    .init_irq       = s5pv210_init_irq,
    .map_io         = smdkv210_map_io,
    .init_machine   = smdkv210_machine_init,
    .timer          = &s5p_timer,
MACHINE_END

在上面的代码中,map_io初始化为smdkv210_map_iocode。如下:

//LinuxSrc/arch/arm/mach-s5pv210/mach-smdkv210.c
static void __init smdkv210_map_io(void)
{
        s5p_init_io(NULL, 0, S5P_VA_CHIPID);
        s3c24xx_init_clocks(24000000);
        s3c24xx_init_uarts(smdkv210_uartcfgs, ARRAY_SIZE(smdkv210_uartcfgs));
        s5p_set_timer_source(S5P_PWM2, S5P_PWM4);
}

在这个函数中包含我们自己定义的创建静态I/O映射表的函数,在移植的时候可能需要我们自己实现。
上代码中s5p_init_io(NULL, 0, S5P_VA_CHIPID);[code]函数中代码如下:

//LinuxSrc/arch/arm/plat-s5p/cpu.c
void __init s5p_init_io(struct map_desc *mach_desc,
                        int size, void __iomem *cpuid_addr)
{
        unsigned long idcode;
        /* initialize the io descriptors we need for initialization */
        iotable_init(s5p_iodesc, ARRAY_SIZE(s5p_iodesc));/* register our io-tables */
        if (mach_desc)
                iotable_init(mach_desc, size);

        idcode = __raw_readl(cpuid_addr);
        s3c_init_cpu(idcode, cpu_ids, ARRAY_SIZE(cpu_ids));
}

其中的iotale_init()[code]就是最终建立页映射的函数。代码如下:

//LiuxSrc/arch/arm/mm/mmu.c
void __init iotable_init(struct map_desc *io_desc, int nr)
{
        int i;
        for (i = 0; i < nr; i++)
               create_mapping(io_desc + i);
}//__

其中create_mapping[code]函数就是通过多个map_desc提供的信息创建线性映射表的。

这个结构体内容的来源在LinuxSrc/arch/arm/mach-s5pv210/cpu.c中。
static struct map_desc s5pv210_iodesc[] __initdata[code]。形如:

//LinuxSrc/arch/arm/mach-s5pv210/cpu.c
//这个数组描述了每个映射的信息
static struct map_desc s5pv210_iodesc[] __initdata = {
        {
                .virtual        = (unsigned long)S5P_VA_SYSTIMER,
                .pfn            = __phys_to_pfn(S5PV210_PA_SYSTIMER),
                .length         = SZ_4K,
                .type           = MT_DEVICE,
        }, {
                .virtual        = (unsigned long)S5P_VA_GPIO,
                //LinuxSrc/arch/arm/plat-s5p/include/plat/map-s5p.h 对用到的虚拟地址做了定义
                //这个值即该I/O资源映射后的内核虚拟地址,创建映射表成功后,便可以在内核或驱动中直接通过该虚拟地址访问这个I/O资源。
                //#define S5P_VA_GPIO             S3C_ADDR(0x02200000)
                .pfn            = __phys_to_pfn(S5PV210_PA_GPIO),
                //LinuxSrc/arch/arm/mach-s5pv210/include/mach/map.h 对用到的物理地址做了定义
                //#define S5PV210_PA_GPIO                 0xE0200000
                .length         = SZ_4K,
                .type           = MT_DEVICE,
        }, {
                .virtual        = (unsigned long)VA_VIC0,
                .pfn            = __phys_to_pfn(S5PV210_PA_VIC0),
                .length         = SZ_16K,
                .type           = MT_DEVICE,
         },
         ...
}

其中的函数:

    #define __phys_to_pfn(paddr)    ((unsigned long)((paddr) >> PAGE_SHIFT))

通过物理地址右移(除以)页大小,得到物理地址页号。

注意有时有的平台IO映射被分为多各部分,也就是说有多个map_desc[]
有的平台通过宏填写map_desc[]。如s3c2410在结构体数组中有IODESC_ENT(LCD),

这个查看源码,在得到结构体需要的虚拟地址VA时,使用了S3C_ADDR的宏:

    //LinuxSrc/arch/arm/plat-samsung/include/plat/map-base.h
    #define S3C_ADDR_BASE   0xF6000000

    #ifndef __ASSEMBLY__
    #define S3C_ADDR(x)     ((void __iomem __force *)S3C_ADDR_BASE + (x))
    #else
    #define S3C_ADDR(x)     (S3C_ADDR_BASE + (x))
    #endif

如对于上面例子GPIO的静态映射:

    //LinuxSrc/arch/arm/plat-s5p/include/plat/map-s5p.h
    #define S5P_VA_GPIO             S3C_ADDR(0x02200000)

最终的虚拟地址的位置是虚基址加上OFFSET(0x02200000)

Source Code Src
http://lxr.oss.org.cn

2 一个简单例子 移植时来实现自己的静态映射

  1. 首先知道要进行静态映射的四个参数。物理地址是硬件定义好的,虚拟地址的使用需要避开冲突。

    源码中S3C_ADDR_BASE是0xF6000000,注释中也提到:
    Fit all our registers in at 0xF6000000 upwards, trying to use as little of the VA space as possible so vmalloc and friends have a better chance of getting memory.
    这表示相距4G,保留了160M的映射空间吗?(之后有描述)
    同时也说明这种映射的结果是在内核空间。

  2. 在声明map的文件中做映射准备,创建map_des,添加到数组,以便系统启动时遍历数组可以完成这个映射的建立。
    定义宏XXX_SRAM_BASE物理地址0x30000000

static struct map_desc xxx_iodesc[] __initdata = {
...
    {
    .virtual = XXX_VA,
    .pfn = __phys_to_pfn(XXX_PA),
    .length = SZ_4K,
    .type = MT_DEVICE
    },
};
  1. 写Module直接测试设备(用户空间的应用程序无法访问设备)
    在Module中直接访问地址(使用了一个网上的例子,原理类似)IO_ADDRESS宏的功能与S3C_ADDR_BASE类似。
//init函数中申请IO内存 -  request_mem_region
struct resource * ret = request_mem_region(SRAM_BASE, SRAM_SIZE, "SRAM Region");//检查内存可用,声明占有
test()
//下面是test()的内容
char str[] = "Hello/n";
void * sram_p;
sram_p = (void *)IO_ADDRESS (XXX_SRAM_BASE);
memcpy(sram_p, str, sizeof(str));
printk(sram_p);
printk("/n");
//exit中 - release_mem_region

或者自己写驱动程序让应用程序间接访问内核空间内存。

附加一些内存布局的内容来探索这部分映射的虚拟地址究竟在哪里:
首先是在VMALLOC_END之后,VMALLOC区域用于vmalloc() / ioremap()

    //LinuxSrc/arch/arm/include/asm/highmem.h
    #define VMALLOC_END     0xF6000000UL

这恰好就是之前S3C_ADDR_BASE的来源。那么有多大的空间可以做映射呢?
在Menory文档中描述:

//LinuxSrc/Documentation/arm/memory.txt
VMALLOC_END     feffffff        Free for platform use, recommended.
                                VMALLOC_END must be aligned to a 2MB
                                boundary.

0xfeffffff - 0xf6000000 = 9*2^24 B = 144MB 这段空间可以用作mapping


notification
source: 《Linux设备驱动开发详解》(第二版),内容为读书笔记和网络资料,有些资料原始来源不详,分享为了方便自己和他人查阅。如有侵权请及时告知,对于带来的不便非常抱歉。转载请注明来源。个人所学有限,若有错误和不足还请不吝赐教,我会及时更正。Terrence Zhou.
http://blog.csdn.net/ts_dchs

reference
[1] 静态地址映射,http://www.cnblogs.com/qiaoge/archive/2012/04/23/2467052.html
[2] (最好教材)源码,http://lxr.oss.org.cn

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
为 flow-server 服务分配 90% 的 CPU 和内存资源是不推荐的,这会导致宿主机上的其他进程无法使用足够的资源,从而影响整个系统的稳定性和性能。为了保证系统的稳定性和可靠性,建议将资源限制设置为合理的范围内。 下面是一个可以参考的示例,其中为 flow-server 服务指定了 2 个 CPU 核心和 1GB 内存: ``` version: '3' services: flow-nginx: container_name: flow-nginx image: nginx build: context: . dockerfile: nginx-dockerfile ports: - "80:80" environment: TZ: Asia/Shanghai volumes: - ./html/dist:/home/flow/projects/ruoyi-ui - ./conf/nginx.conf:/etc/nginx/nginx.conf - ./home/nginx/logs:/var/log/nginx - ./home/nginx/conf.d:/etc/nginx/conf.d depends_on: - flow-server links: - flow-server networks: - base-docker_flownet flow-server: container_name: flow-server build: context: . dockerfile: flow-dockerfile ports: - "8080:8089" environment: TZ: Asia/Shanghai volumes: - ./home/flow/logs:/home/flow/logs - ./home/flow/uploadPath:/home/flow/uploadPath - /home/flow/flow_ftp:/home/flow/flow_ftp networks: - base-docker_flownet deploy: resources: limits: cpus: '2' memory: 1g reservations: cpus: '1' memory: 500m ``` 在这个示例中,我们将 flow-server 服务的 CPU 核心数限制为 2,内存限制为 1GB。这些限制可以根据您的实际需求进行调整。同时,我们还指定了 CPU 和内存的保留量,这可以确保 flow-server 服务在运行时至少有 1 个 CPU 核心和 500MB 内存可用。 请注意,在 Docker 容器中,CPU 和内存的使用方式与在物理服务器上有所不同。如果您的应用程序需要使用大量的 CPU 和内存资源,请确保在容器中进行有效的资源管理和优化,以确保容器和宿主机的稳定性和可靠性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值