arm-linux(kernel-2.6.13)的启动过程(1.3/2)
回到paging_init中...
前面已经为 ram 创建了页表(一级表),linux已经完全可以访问ram的任何角落了,但是io空间还看不到呢,比如无法访问gpio的寄存器(0x56000000)。
下面就解决了这个问题。我们先看看linux对虚拟地址的使用方法
Start End Use
--------------------------------------------------------------------------
ffff8000 ffffffff copy_user_page / clear_user_page use.
For SA11xx and Xscale, this is used to setup a minicache mapping.
ffff1000 ffff7fff 保留不可用. Platforms must not use this address range.
ffff0000 ffff0fff CPU vector page.
ffc00000 fffeffff DMA 内存映射用到的区间,dma_alloc_xxx functions用到的区域
ff000000 ffbfffff Reserved for future expansion of DMA mapping region.
e0000000 feffffff 推荐给我们的平台(开发板)使用的虚拟空间范围。
c8000000 e0000000-1 vmalloc() / ioremap() space.这里并没有与下面的相连续,是经过8M对齐的(向上对齐)
c0000000 c4000000-1 内核直接映射的ram区域,0xc0000000~0xc4000000:0x30000000~0x34000000
bf000000 c0000000-1 insmod用的空间,内核动态加载的modules在这里。
00001000 bf000000-1 用户空间可用的虚拟地址范围,通过mmap()来映射。
00000000 00000fff 用于中断向量的虚拟地址
if (mdesc->map_io)
mdesc->map_io();
local_flush_tlb_all();
这个函数在机器描述符里面,每种机器(开发板)都有一个自己的machine_desc结构体,里面定义了自己的.map_io 函数指针,
作用是
1。把自己的io空间 告诉 linux
2。初始化系统的各个部分的时钟。
struct machine_desc {
...
.map_io = sbc2440_map_io,
...
}
这个函数在mach-xxx.c(我的是mach-qq2440.c)中
void __init sbc2440_map_io(void)
{
s3c24xx_init_io(sbc2440_iodesc, ARRAY_SIZE(sbc2440_iodesc));
#ifdef CONFIG_S3C2440_INCLK12
s3c24xx_init_clocks(12000000);
#else
s3c24xx_init_clocks(16934400);
#endif
s3c24xx_init_uarts(sbc2440_uartcfgs, ARRAY_SIZE(sbc2440_uartcfgs));
s3c24xx_set_board(&sbc2440_board); //这个什么都没有作(只是注册板子)
s3c_device_nand.dev.platform_data = &bit_nand_info; //顺便把nand的参数也挂进来,包含了分区信息。
}
这个函数又要一点一点的分析了。参数sbc2440_iodesc是个map_desc结构数组,他的内容如下,
ARRAY_SIZE()算出结构数组中map_desc结构的个数。
static struct map_desc sbc2440_iodesc[] __initdata = {
{vSMDK2410_ETH_IO, pSMDK2410_ETH_IO, SZ_1M, MT_DEVICE},
{0xe0000000, 0x08000000, 0x00100000, MT_DEVICE},
{0xe0100000, 0x10000000, 0x00100000, MT_DEVICE},
{ (unsigned long)S3C24XX_VA_IIS, S3C2410_PA_IIS, S3C24XX_SZ_IIS, MT_DEVICE },
};
这里的映射的大小都是用的SZ_1M,因为这样我们可以只使用一个段描述符,不用使用粗页了,节省了内存空间,浪费了虚拟空间。
最后一个条目(iis)是主要的,其他的我看没必要。
s3c24xx_init_io(sbc2440_iodesc, ARRAY_SIZE(sbc2440_iodesc));
/* cpu information */
static struct cpu_table *cpu;
void __init s3c24xx_init_io(struct map_desc *mach_desc, int size)
{
unsigned long idcode;
/* initialise the io descriptors we need for initialisation */
iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc)); //放在后面解释
idcode = __raw_readl(S3C2410_GSTATUS1);
在iotable_init之前,这个读取io空间寄存器的操作显然是不可以完成的,现在ok了。
cpu = s3c_lookup_cpu(idcode);
从idcode分辨出是什么cpu,然后赋值cpu:表示找到了合适cpu。
if (cpu == NULL) {
printk(KERN_ERR "Unknown CPU type 0x%08lx\n", idcode);
panic("Unknown S3C24XX CPU");
}
if (cpu->map_io == NULL || cpu->init == NULL) {
printk(KERN_ERR "CPU %s support not enabled\n", cpu->name);
panic("Unsupported S3C24XX CPU");
}
printk("CPU %s (id 0x%08lx)\n", cpu->name, idcode);
(cpu->map_io)(mach_desc, size); //随后,调用了cpu相关的io映射,后面分析
}
iotable_init这个函数映射了基本的io空间
/* minimal IO mapping */
static struct map_desc s3c_iodesc[] __initdata = {
IODESC_ENT(GPIO),
IODESC_ENT(IRQ),
IODESC_ENT(MEMCTRL),
IODESC_ENT(UART)
};
顾名思义,他包括了gpio,irq,memctrl,uart,四部分内容,这是最基本的io空间,也是映射的第一部分io空间哦!
在map.h中定义了这些量。他们都被放到了
e0000000 feffffff 推荐给我们的平台(开发板)使用的虚拟空间范围。
这个区间,而且大小都是1m,为了使用段描述符,浪费些虚拟地址空间没什么问题。
看看cpu相关的io映射部分。
(cpu->map_io)(mach_desc, size)
void __init s3c2440_map_io(struct map_desc *mach_desc, int size)
{
/* register our io-tables */
iotable_init(s3c2440_iodesc, ARRAY_SIZE(s3c2440_iodesc));
iotable_init(mach_desc, size); //这里映射了iis部分,第三次映射io空间。
/* rename any peripherals used differing from the s3c2410 */
s3c_device_i2c.name = "s3c2440-i2c";
s3c_device_nand.name = "s3c2440-nand";
/* change irq for watchdog */
s3c_device_wdt.resource[1].start = IRQ_S3C2440_WDT;
s3c_device_wdt.resource[1].end = IRQ_S3C2440_WDT;
}
2440相关的io如下,这些内容被认为是可能与2410不同的io部分。其实大部分相同,这是第2次映射io地址。
static struct map_desc s3c2440_iodesc[] __initdata = {
IODESC_ENT(USBHOST),
IODESC_ENT(USBDEV), // jdh added ==> ghcstop
IODESC_ENT(CLKPWR),
IODESC_ENT(LCD),
IODESC_ENT(TIMER),
IODESC_ENT(ADC),
IODESC_ENT(WATCHDOG),
};
随后分别为平台设备 iic,和nand 更改名字。为看门狗更改中断资源(2440的中断组织方式于2410的不同)。有时间在研究。
回到 sbc2440_map_io,随后初始化系统的时钟。记得以前我因为这个的设置不正确,导致串口乱码。现在看看,到底是怎么回事?
s3c24xx_init_clocks(12000000);
xtal = 0 -> use default PLL crystal value (normally 12MHz)
void __init s3c24xx_init_clocks(int xtal)
{
if (xtal == 0)
xtal = 12*1000*1000;
if (cpu == NULL)
panic("s3c24xx_init_clocks: no cpu setup?\n");
if (cpu->init_clocks == NULL)
panic("s3c24xx_init_clocks: cpu has no clock init\n");
else
(cpu->init_clocks)(xtal); //使用cpu结构中的 初始化时钟 函数,初始化时钟。
}
这里调用的是s3c2440_init_clocks()
void __init s3c2440_init_clocks(int xtal)
{
unsigned long clkdiv;
unsigned long camdiv;
unsigned long hclk, fclk, pclk;
int hdiv = 1;
/* now we've got our machine bits initialised, work out what
* clocks we've got */
fclk = s3c2410_get_pll(__raw_readl(S3C2410_MPLLCON), xtal) * 2;
clkdiv = __raw_readl(S3C2410_CLKDIVN);
camdiv = __raw_readl(S3C2440_CAMDIVN);
/* work out clock scalings */
switch (clkdiv & S3C2440_CLKDIVN_HDIVN_MASK) {
case S3C2440_CLKDIVN_HDIVN_1:
hdiv = 1;
break;
case S3C2440_CLKDIVN_HDIVN_2:
hdiv = 2;
break;
case S3C2440_CLKDIVN_HDIVN_4_8:
hdiv = (camdiv & S3C2440_CAMDIVN_HCLK4_HALF) ? 8 : 4;
break;
case S3C2440_CLKDIVN_HDIVN_3_6:
hdiv = (camdiv & S3C2440_CAMDIVN_HCLK3_HALF) ? 6 : 3;
break;
}
hclk = fclk / hdiv;
pclk = hclk / ((clkdiv & S3C2440_CLKDIVN_PDIVN)? 2:1);
/* print brief summary of clocks, etc */
printk("S3C2440: core %ld.%03ld MHz, memory %ld.%03ld MHz, peripheral %ld.%03ld MHz\n",
print_mhz(fclk), print_mhz(hclk), print_mhz(pclk));
/* initialise the clocks here, to allow other things like the
* console to use them, and to add new ones after the initialisation
*/
s3c24xx_setup_clocks(xtal, fclk, hclk, pclk); //在这里初始化了所有系统用到的时钟。
读这个函数的代码可以发现,即使是系统启动以后,我们用的mpll仍然是uboot初始化的(400MHz),这里的代码只是解释下400MHz的含义,
设置非arm核的始终,包括了usb,adc,iic,spi。
}
回到paging_init中...
/*
* initialise the zones within each node
*/
其实以下的代码已经不是移植linux相关的了(平台移植),也就是不需要更改的。
#define MAX_NR_ZONES 3
接下来为每个内存节点创建管理区,每个内存节点有3个管理区,分别是
#define ZONE_DMA 0
#define ZONE_NORMAL 1
#define ZONE_HIGHMEM 2
以上三个管理区。
for_each_online_node(node) { //为每个内存节点(UMA)建立管理区。
unsigned long zone_size[MAX_NR_ZONES];
unsigned long zhole_size[MAX_NR_ZONES];
struct bootmem_data *bdata;
pg_data_t *pgdat;
int i;
/*
* Initialise the zone size information.
*/
for (i = 0; i < MAX_NR_ZONES; i++) {//初始化本节点的数据
zone_size[i] = 0;
zhole_size[i] = 0;
}
pgdat = NODE_DATA(node); //不陌生吧?pg_data_t是pglist_data的重定义,用于描述具体的内存节点
bdata = pgdat->bdata;
/*
* The size of this node has already been determined.
* If we need to do anything fancy with the allocation
* of this memory to the zones, now is the time to do
* it.
*/
本节点的大小已经被确定了,如果想要改变本节点的管理区的大小,现在是时候了。
bdata->node_boot_start = (start << PAGE_SHIFT); 物理起始地址0x30000000<<12
bdata->node_low_pfn = end; 结束页面号。
这样来看,下面的语句得到的是本管理区的大小(页面为单位)
zone_size[0] = bdata->node_low_pfn -
(bdata->node_boot_start >> PAGE_SHIFT);
/*
* If this zone has zero size, skip it.
*/
if (!zone_size[0])//不管理大小为0的区域
continue;
/*
* For each bank in this node, calculate the size of the
* holes. holes = node_size - sum(bank_sizes_in_node)
*/计算本节点上每个区域的孔洞,一个内存节点可以后多个bank,我的ram只有一个bank
zhole_size[0] = zone_size[0];
for (i = 0; i < mi->nr_banks; i++) {
if (mi->bank[i].node != node)
continue;
zhole_size[0] -= mi->bank[i].size >> PAGE_SHIFT;
}
我的ram只有一个bank。
可以想见,本节点的孔洞是0(页)。没有孔哦!
/*
* Adjust the sizes according to any special
* requirements for this machine type.
*/根据一些特殊的需要,为本节点调整大小。
arch_adjust_zones(node, zone_size, zhole_size);//空函数
free_area_init_node(node, pgdat, zone_size,
bdata->node_boot_start >> PAGE_SHIFT, zhole_size);
建立管理区的主要函数是这个free_area_init_node ->free_area_init_core。
看下这个函数。
free_area_init_node(0,pgdat,zone_size,0x30000000,0)
void __init free_area_init_node(int nid, struct pglist_data *pgdat,
unsigned long *zones_size, unsigned long node_start_pfn,
unsigned long *zholes_size)
{
pgdat->node_id = nid;
pgdat->node_start_pfn = node_start_pfn;
calculate_zone_totalpages(pgdat, zones_size, zholes_size);
我的配置如下
CONFIG_FLATMEM=y
CONFIG_FLAT_NODE_MEM_MAP=y
以上主要是填充pgdat结构。
alloc_node_mem_map(pgdat);
这个函数完成了对page结构所需要的空间的分配,且调整了全局mem_map指针。
对空间的分配,最终调用的是__alloc_bootmem_core这个函数,前面分析过了。
free_area_init_core(pgdat, zones_size, zholes_size);//天啊,这是个大家伙,下面分析下。
}
/*
* Set up the zone data structures:
* - mark all pages reserved
* - mark all memory queues empty
* - clear the memory bitmaps
*/
static void __init free_area_init_core(struct pglist_data *pgdat,
unsigned long *zones_size, unsigned long *zholes_size)
{
unsigned long i, j;
int cpu, nid = pgdat->node_id;
unsigned long zone_start_pfn = pgdat->node_start_pfn;
pgdat->nr_zones = 0; //初始化成0个管理区
init_waitqueue_head(&pgdat->kswapd_wait);
pgdat->kswapd_max_order = 0;
for (j = 0; j < MAX_NR_ZONES; j++) { //扫描所有管理区。
struct zone *zone = pgdat->node_zones + j; //取得本节点的某个管理区dma,normal,hightmem之一
unsigned long size, realsize;
unsigned long batch;
realsize = size = zones_size[j];
if (zholes_size)
realsize -= zholes_size[j]; //计算出本管理区的真实大小:去掉孔。
if (j == ZONE_DMA || j == ZONE_NORMAL)
nr_kernel_pages += realsize;
nr_all_pages += realsize; //统计这些信息,内核的页面数(包括dma,normal区间的页面) 和 所有页面数(内核的页面 + 高端页面数)
zone->spanned_pages = size;
zone->present_pages = realsize;
zone->name = zone_names[j];
spin_lock_init(&zone->lock);
spin_lock_init(&zone->lru_lock);
zone->zone_pgdat = pgdat;
zone->free_pages = 0;
zone->temp_priority = zone->prev_priority = DEF_PRIORITY;
//以上初始化本管理区的内容
batch = zone_batchsize(zone); //似乎和cpu缓存有关???
for (cpu = 0; cpu < NR_CPUS; cpu++) { //为每个cpu设置pageset??
setup_pageset(zone_pcp(zone,cpu), batch);
}
printk(KERN_DEBUG " %s zone: %lu pages, LIFO batch:%lu\n",
zone_names[j], realsize, batch);
INIT_LIST_HEAD(&zone->active_list);
INIT_LIST_HEAD(&zone->inactive_list);
zone->nr_scan_active = 0;
zone->nr_scan_inactive = 0;
zone->nr_active = 0;
zone->nr_inactive = 0;
atomic_set(&zone->reclaim_in_progress, -1);
if (!size) //跳过0大小的管理区间
continue;
/*
* The per-page waitqueue mechanism uses hashed waitqueues
* per zone.
*/
zone->wait_table_size = wait_table_size(size);
zone->wait_table_bits =
wait_table_bits(zone->wait_table_size);
zone->wait_table = (wait_queue_head_t *)//为这个等待队列分配内存
alloc_bootmem_node(pgdat, zone->wait_table_size
* sizeof(wait_queue_head_t));
for(i = 0; i < zone->wait_table_size; ++i) //初始化本管理区的所有队列头
init_waitqueue_head(zone->wait_table + i);
pgdat->nr_zones = j+1; //j+1表示本节点的管理区数量,因为跳过了0大小的管理区间,所以for循环的最后,nr_zones=1
zone->zone_mem_map = pfn_to_page(zone_start_pfn);
zone->zone_start_pfn = zone_start_pfn;
memmap_init(size, nid, j, zone_start_pfn);
这个函数保留了所有的页面,建立了本页面属于那个管理区,本管理区属于那个内存节点的联系。也就是page-flags里的内容
zonetable_add(zone, nid, j, zone_start_pfn, size);
把这个管理区添加到zone_table
struct zone *zone_table[1 << ZONETABLE_SHIFT];
EXPORT_SYMBOL(zone_table);
上面是zone_table的定义。
zone_start_pfn += size;
zone_init_free_lists(pgdat, zone, zone->spanned_pages);
}
}
为每个节点建立管理区这里结束了。
}
/*
* finish off the bad pages once
* the mem_map is initialised
*/
memzero(zero_page, PAGE_SIZE);//初始化0页
empty_zero_page = virt_to_page(zero_page);
flush_dcache_page(empty_zero_page);
}
到了这里已经完成了paging_init(),
总结这个函数的功能:他为内核建立ram的页表 和 io空间的页表,还初始化了系统其他部分用到的时钟。
回到 setup_arch()
阅读(2348) | 评论(0) | 转发(0) |