本来想在不初始化这些部件的情况下把SylixOS先启动起来感受下,结果测试发现如果MMU不使能的话,系统启动过程中线程无法进行调度emm。。。所以只好把这一章节提前来讲了。这三个组件的初始化都是在bspInit.c中进行的。
我们首先来看下FPU的初始化:
static VOID halFpuInit (VOID)
{
API_KernelFpuInit(ARM_MACHINE_A7, ARM_FPU_VFPv3);
}
可以看出来FPU的初始化很简单,只需要调用API_KernelFpuInit 即可。第一个参数表示当前CPU的架构,第二个参数表示当前SOC中FPU使用的类型,这两个参数根据芯片数据手册的信息然后使用内核提供好的宏填入就行了。
再来看看Cache的初始化:
static VOID halCacheInit (VOID)
{
API_CacheLibInit(CACHE_COPYBACK, CACHE_COPYBACK, ARM_MACHINE_A7); /* 初始化 CACHE 系统 */
API_CacheEnable(INSTRUCTION_CACHE);
API_CacheEnable(DATA_CACHE); /* 使能 CACHE */
}
首先使用API_CacheLibInit 接口初始化了内核Cache组件:
初始化完了内核Cache组件,接着使用了API_CacheEnable 接口使能了指令Cache和数据Cache,可以看出Cache的初始化还是比较简单的,因为内核已经为大部分arm架构封装好了Cache等部件的操作,我们只需要调用接口即可。
另外有些平台上的L2 Cache是可以单独控制的,比如zynq7000平台,针对这些L2 Cache可以控制的的平台我们还需要实现bspLib.c中的bspL2CBase 和bspL2CAux 这两个接口:
全志R16平台并没有单独L2 Cache控制器,所以这两个接口我们直接使用默认实现即可。
MMU的初始化需要做三部分工作,一个是bspMap.h 中映射表的设置,另外一个是内核VMM组件初始化,最后是MMU页表池大小设置,下面我们分别来学习下这两部分的内容。
映射表是定义在bspMap.h 中,_G_physicalDesc 描述物理地址空间的关系,_G_virtualDesc 描述虚拟地址空间关系,VMM通过这两个表中定义的关系来管理物理地址和虚拟地址。
SylixOS中使用LW_MMU_PHYSICAL_DESC 这个数据结构来描述中断向量表、物理内存区、外设寄存器的物理地址和虚拟地址的映射关系:
/*********************************************************************************************************
物理内存信息描述
注意:
TEXT, DATA, DMA 物理段 PHYD_ulPhyAddr 必须等于 PHYD_ulVirMap,
TEXT, DATA, VECTOR, BOOTSFR, DMA 物理段 PHYD_ulVirMap 区间不能与虚拟内存空间冲突.
*********************************************************************************************************/
typedef struct __lw_vmm_physical_desc {
addr_t PHYD_ulPhyAddr; /* 物理地址 (页对齐地址) */
addr_t PHYD_ulVirMap; /* 需要初始化的映射关系 */
size_t PHYD_stSize; /* 物理内存区长度 (页对齐长度) */
#define LW_PHYSICAL_MEM_TEXT 0 /* 内核代码段 */
#define LW_PHYSICAL_MEM_DATA 1 /* 内核数据段 (包括 HEAP) */
#define LW_PHYSICAL_MEM_VECTOR 2 /* 硬件向量表 */
#define LW_PHYSICAL_MEM_BOOTSFR 3 /* 启动时需要的特殊功能寄存器 */
#define LW_PHYSICAL_MEM_BUSPOOL 4 /* 总线地址池, 不进行提前映射 */
#define LW_PHYSICAL_MEM_DMA 5 /* DMA 物理内存, 不进行提前映射*/
#define LW_PHYSICAL_MEM_APP 6 /* 装载程序内存, 不进行提前映射*/
UINT32 PHYD_uiType; /* 物理内存区间类型 */
UINT32 PHYD_uiReserve[8];
} LW_MMU_PHYSICAL_DESC;
typedef LW_MMU_PHYSICAL_DESC *PLW_MMU_PHYSICAL_DESC;
注意:这里的地址和大小值必须是当前平台页对齐的值,系统启动过程中会检测是否对齐,如果不对齐则会启动失败。
让我们来看看R16平台上这个表的具体设置:
LW_MMU_PHYSICAL_DESC _G_physicalDesc[] = {
{ /* 中断向量表 */
BSP_CFG_RAM_BASE,
0,
LW_CFG_VMM_PAGE_SIZE,
LW_PHYSICAL_MEM_VECTOR
},
{ /* 内核代码段 */
BSP_CFG_RAM_BASE,
BSP_CFG_RAM_BASE,
BSP_CFG_TEXT_SIZE,
LW_PHYSICAL_MEM_TEXT
},
{ /* 内核数据段 */
BSP_CFG_RAM_BASE + BSP_CFG_TEXT_SIZE,
BSP_CFG_RAM_BASE + BSP_CFG_TEXT_SIZE,
BSP_CFG_DATA_SIZE,
LW_PHYSICAL_MEM_DATA
},
{ /* DMA 缓冲区 */
BSP_CFG_RAM_BASE + BSP_CFG_TEXT_SIZE + BSP_CFG_DATA_SIZE,
BSP_CFG_RAM_BASE + BSP_CFG_TEXT_SIZE + BSP_CFG_DATA_SIZE,
BSP_CFG_DMA_SIZE,
LW_PHYSICAL_MEM_DMA
},
{ /* APP 通用内存 */
BSP_CFG_RAM_BASE + BSP_CFG_TEXT_SIZE + BSP_CFG_DATA_SIZE + BSP_CFG_DMA_SIZE,
BSP_CFG_RAM_BASE + BSP_CFG_TEXT_SIZE + BSP_CFG_DATA_SIZE + BSP_CFG_DMA_SIZE,
BSP_CFG_APP_SIZE,
LW_PHYSICAL_MEM_APP
},
/*
* TODO: 加入启动设备的寄存器映射, 参考代码如下:
*/
{ /* UART0 ~ 4 */
0x01C28000,
0x01C28000,
2 * LW_CFG_VMM_PAGE_SIZE,
LW_PHYSICAL_MEM_BOOTSFR
},
{ /* 结束 */
0,
0,
0,
0
}
};
SylixOS中使用LW_MMU_VIRTUAL_DESC 数据结构来描述虚拟地址空间信息:
typedef struct __lw_mmu_virtual_desc {
addr_t VIRD_ulVirAddr; /* 虚拟空间地址 (页对齐地址) */
size_t VIRD_stSize; /* 虚拟空间长度 (页对齐长度) */
#define LW_VIRTUAL_MEM_APP 0 /* 装载程序虚拟内存区间 */
#define LW_VIRTUAL_MEM_DEV 1 /* 设备映射虚拟内存空间 */
UINT32 VIRD_uiType; /* 虚拟内存区间类型 */
UINT32 VIRD_uiReserve[8];
} LW_MMU_VIRTUAL_DESC;
typedef LW_MMU_VIRTUAL_DESC *PLW_MMU_VIRTUAL_DESC;
同样的,起始地址和大小值也必须是页对齐的值。VMM管理的虚拟地址空间只有两种:LW_VIRTUAL_MEM_APP 表示系统动态加载的程序、动态库和内核模块所使用的虚拟地址空间;LW_VIRTUAL_MEM_DEV 表示系统调用API_VmmIoRemap 这类接口动态映射寄存器地址时所使用的虚拟地址空间。
我们来看看R16平台上的实际配置:
LW_MMU_VIRTUAL_DESC _G_virtualDesc[] = {
/*
* TODO: 加入虚拟地址空间的定义, 参考代码如下:
*/
#if 1
{ /* 应用程序虚拟空间 */
0x80000000,
((size_t)1 * LW_CFG_GB_SIZE),
LW_VIRTUAL_MEM_APP
},
{
0xC0000000, /* ioremap 空间 */
(64 * LW_CFG_MB_SIZE),
LW_VIRTUAL_MEM_DEV
},
#endif
{ /* 结束 */
0,
0,
0
}
};
注意:这里的虚拟地址和物理空间描述表中各个区域的虚拟地址不要有重复,在满足这个前提下,地址值可以任意配置。另外还需要注意的是起始地址加上大小之后不要超过4GB,因为我们现在用的是32位的地址空间。
在配置好上述的两个映射表后,就可以使用API_VmmLibInit 这个接口来初始化VMM组件:
static VOID halVmmInit (VOID)
{
API_VmmLibInit(_G_physicalDesc, _G_virtualDesc, ARM_MACHINE_A7);
API_VmmMmuEnable();
}
上述代码应该很好理解,就不再赘述。初始化好VMM组件之后,最后调用API_VmmMmuEnable 接口来使能MMU。
MMU正常工作需要使用页表,全志R16属于armv7架构,在armv7架构下,SylixOS内核使用的是二级页表机制。第一级页表叫全局页目录(Page Global Directory),也可以叫做一级页表,页目录中单个条目就叫做页目录项,一个页目录项可以映射1MB的物理页,32位地址空间有4GB大小,所以整个页目录共有4096个页目录项。第二级页表叫做二级页表,二级页表中单个条目就叫做页表项(Page Table Entry),一般arm32位都是用4KB页大小,也就是一个页表项可以映射4KB的物理页,一个二级页表可以映射1MB大小,所以一个二级页表中共有256个页表项:
在SylixOS中,一级页表和二级页表所占的内存是在内核堆上的,也就是内核数据区空间,所以在配置config.h时,最好将内核数据区配置大点,否则有可能因为MMU页表申请不到内存空间而导致系统启动失败。
在一个实际的嵌入式项目中,并不会完全使用4GB的空间,那么我们就可以将二级页表的个数减少点来节省下内存给其他的内核模块或者应用使用,这是通过bspLib.c中的bspMmuPgdMaxNum 和bspMmuPteMaxNum 这两个接口实现的。
bspMmuPgdMaxNum 接口主要用来获取一级页表的个数,一个一级页表已经能映射4GB的空间了,所以这个函数一般都是返回1:
bspMmuPteMaxNum 接口主要用来获取二级页表的个数,注意是二级页表的个数而不是页表项的个数。如果是4096则表示映射完整的4GB空间,这里实际返回的是2048,也就是表示只映射2GB的空间:
因为本次使用的开发板内存是1GB,再加上其他的寄存器空间大小,2GB空间已经足够使用了。如果开发板内存是2GB的话,那么这个函数的返回值就需要改大一点了,总之这个值根据实际情况进行设置。