Chinaunix首页 | 论坛 | 博客
  • 博客访问: 408570
  • 博文数量: 62
  • 博客积分: 1483
  • 博客等级: 上尉
  • 技术积分: 779
  • 用 户 组: 普通用户
  • 注册时间: 2009-02-24 12:25
文章分类

全部博文(62)

文章存档

2012年(2)

2011年(6)

2010年(6)

2009年(48)

我的朋友

分类: LINUX

2009-10-10 12:12:36

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) |
给主人留下些什么吧!~~