F1C100S rt-smart 内核移植(二)

前言

本篇的内容进入了rt-smart内核的C语言世界,因此会同时涉及到较多的.c文件,需要读者对rt-smart内核有基本的认识,至少需要大致了解内核的文件结构。

在上一章节中,我们从启动汇编start_gcc.S进入了内核入口rtthread_startup,该内核入口函数位于./kernel/src/components.c文件中;一般而言,为了保证内核的代码统一和可移植性,通常不会直接修改rtthread_startup里面的内容,而是根据主板硬件和用户实际需求将自身特定的代码实现在函数rt_hw_board_init中;跳转到components.c中就可以看到以下内容:

int rtthread_startup(void)
{
	rt_hw_interrupt_disable();
    /* board level initialization
     * NOTE: please initialize heap inside board initialization.
     */
    rt_hw_board_init();

    /* show RT-Thread version */
    rt_show_version();

    /* timer system initialization */
    rt_system_timer_init();

    /* scheduler system initialization */
    rt_system_scheduler_init();

#ifdef RT_USING_SIGNALS
    /* signal system initialization */
    rt_system_signal_init();
#endif

    /* create init_thread */
    rt_application_init();

    /* timer thread initialization */
    rt_system_timer_thread_init();

    /* idle thread initialization */
    rt_thread_idle_init();

#ifdef RT_USING_SMP
    rt_hw_spin_lock(&_cpus_lock);
#endif /*RT_USING_SMP*/

    /* start scheduler */
    rt_system_scheduler_start();

    /* never reach here */
    return 0;
}

关闭中断

rtthread_startup进来第一步便是关闭中断rt_hw_interrupt_disable,屏蔽中断FIQ和IRQ;由于我们在start_gcc.S启动代码中已经关闭了中断控制器(INTC),这里实际上运行到此处是不会有外部中断产生的。'rt_hw_interrupt_disable'函数实现位于context_gcc.S中,由于后面开启调度器会强行使能中断,因此这里可以不接收返回的CPSR寄存器值。

板级初始化

第二步调用的函数rt_hw_board_init,在rtthread标准版中主要执行芯片相关的初始化操作,例如获取CPU主频,开启systick定时器,执行BOARD_EXPORT宏导出的初始化函数。在rt-smart系统中,除了上述工作还新增了二级页表配置,ioremap配置,LWP用户态初始化等,板级初始化函数也是rt-smart适配到不同芯片时比较重要的步骤。
rt_hw_board_init函数的实现通常实现在bsp目录下,例如./kernel/bsp/allwinner_tina/driver/board.c中,下面代码中已经定义的宏:RT_USING_USERSPACE RT_USING_HEAP RT_USING_CONSOLE RT_USING_COMPONENTS_INIT

// 以下全局变量、宏定义、结构体定义是从其它文件复制而来,
// 在真实内核文件结构中并不在一起,这里放在一起是为了便于说明

#define HEAP_END        (void*)(KERNEL_VADDR_START + 8 * 1024 * 1024)
#define PAGE_START      HEAP_END
#define PAGE_END        (void*)(KERNEL_VADDR_START + 32 * 1024 * 1024)

#ifdef RT_USING_USERSPACE
rt_region_t init_page_region = {
    (size_t)PAGE_START,
    (size_t)PAGE_END,
};
#endif

typedef struct
{
    size_t *vtable;
    size_t vstart;
    size_t vend;
    size_t pv_off;
} rt_mmu_info;

rt_mmu_info mmu_info;

void rt_hw_board_init(void)
{

#ifdef RT_USING_USERSPACE
    // 0xf0000000 ~ 0xffffffff - 1 高256MB保留作为动态设备映射地址空间
    rt_hw_mmu_map_init(&mmu_info, (void*)0xf0000000, 0x10000000, (size_t *)MMUTable, PV_OFFSET);

    rt_page_init(init_page_region);

    rt_hw_mmu_ioremap_init(&mmu_info, (void*)0xf0000000, 0x10000000);

    arch_kuser_init(&mmu_info, (void*)0xffff0000);

#else
    rt_hw_mmu_map_init(&mmu_info, (void*)0x80000000, 0x10000000, MMUTable, 0);
    rt_hw_mmu_ioremap_init(&mmu_info, (void*)0x80000000, 0x10000000);
#endif

#ifdef RT_USING_HEAP
    /* initialize system heap */
    rt_system_heap_init(HEAP_BEGIN, HEAP_END);
#endif

    rt_hw_interrupt_init();

    ccu_init();

    rt_hw_gpio_init();

    /* init hardware interrupt */
    rt_hw_uart_init();

#ifdef RT_USING_CONSOLE
    /* set console device */
    rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
#endif /* RT_USING_CONSOLE */

    os_clock_init();

#ifdef RT_USING_COMPONENTS_INIT
    rt_components_board_init();
#endif

}

rt_hw_mmu_map_init

rt_hw_mmu_map_init主要是预留0xf0000000 ~ 0xffffffff高256M地址空间作为外设地址空间,其中的实现也并不复杂,只是配置下mmu_info这个结构体,在后面使用虚拟地址映射实际的物理外设时配合mmu_info里面的信息进行检查和分配。
其函数原型如下

/**
 * @param mmu_info 用于存储地址信息的mmu_info结构体
 * @param v_address 起始虚拟地址
 * @param size 空间大小 unit:byte
 * @param vtable 当前使用的MMU页表(内核页表)
 * @param pv_off 内核起始虚拟地址和DRAM物理起始地址的差值 0xC0000000
 * */
int rt_hw_mmu_map_init(rt_mmu_info *mmu_info, void* v_address, size_t size, size_t *vtable, size_t pv_off)

rt_hw_mmu_map_init的初始化操作还是比较简单的,该函数的返回值上层也没有处理,事实上这里不应该出错,这是内核移植者应当保证的。(因为此时串口都还没初始化,在这之前即使发生错误也无法告知用户了。)
image

rt_page_init

rt_page_init为页内存(页面)初始化,rt-smart和32位linux系统一样,使用4KB的页大小,因此虚拟地址空间和物理地址空间的分页都是4KB;页面内存的范围由init_page_region结构体指定,start表示页内存起始地址,end表示页内存的结束地址(不包含end地址本身)。

typedef struct tag_region
{
    size_t start;
    size_t end;
} rt_region_t;

通过上面的HEAP_BEGIN, HEAP_END, PAGE_START, PAGE_END宏定义,我们可以推断出rt-smart的内存布局情况:
image
rt_page_init使用struct page链表结构(连续内存的数组实现)记录下“页内存空间”可以被划分的4K页面数量,同时在“内核代码/数据区”记录下头指针用于索引整个链表。
image
rt-smart的页内存管理是基于伙伴分配器的思想,后面有时间再单独开一篇,这里只介绍下它的初始化过程,在实际系统移植过程中,page.c是不用修改的。

// page结构体在32位平台占用16字节
struct page
{
    struct page *next;  /* same level next */
    struct page *pre;   /* same level pre  */
    uint32_t size_bits; /* if is ARCH_ADDRESS_WIDTH_BITS, means not free */
    int ref_cnt;        /* page group ref count */
};

void rt_page_init(rt_region_t reg)
{
    int i;
	// 将页内存空间起始地址向上对齐到4KB边界,结束地址向下对齐到4KB边界
    reg.start += ARCH_PAGE_MASK;
    reg.start &= ~ARCH_PAGE_MASK;

    reg.end &= ~ARCH_PAGE_MASK;

    {
        
		// 计算一个物理页4KB能够存储的page结构体数量,4K / 16B = 256
        int nr = ARCH_PAGE_SIZE / sizeof(struct page); // 256
		// 计算总共有多少个可用物理页,这里我给页内存空间指定了24MB内存,
		// 那么可划分成24 * 1024 / 4 = 6144个物理页
        int total = (reg.end - reg.start) >> ARCH_PAGE_SHIFT; // 6144
		// 计算存放page结构需要的物理页数量
		// 一个物理页能存放256个page结构体,那么6144个页需要6144个page结构体
		// 也就需要 6144 / 256 = 24个物理页,这部分空间留给页分配器管理剩下的空间
		// 当然了"mnr"总是偏大的,因为它把自己占用的空间也算到page结构体管理范围了
		// 页分配器不可能自己管理自身占用的内存,不过(total + nr) / (nr + 1)都对齐到4K了,也不用在意那几百个字节
        int mnr = (total + nr) / (nr + 1); // 24

        RT_ASSERT(mnr < total);

        page_start = (struct page*)reg.start;
        reg.start += (mnr << ARCH_PAGE_SHIFT); // + 98304
        page_addr = (void*)reg.start;
        page_nr = (reg.end - reg.start) >> ARCH_PAGE_SHIFT;
    }


    /* init free list */
    for (i = 0; i < ARCH_PAGE_LIST_SIZE; i++)
    {
        page_list[i] = 0;
    }

    /* 初始化可供分配的物理页管理结构体 */
    for (i = 0; i < page_nr; i++)
    {
        page_start[i].size_bits = ARCH_ADDRESS_WIDTH_BITS;
        page_start[i].ref_cnt = 1;
    }

    struct page *p;
    unsigned int align_bits;
    unsigned int size_bits;

    /* add pages to free list */
    while (reg.start != reg.end)
    {

        size_bits = ARCH_ADDRESS_WIDTH_BITS - 1 - rt_clz(reg.end - reg.start);
        align_bits = rt_ctz(reg.start);
        if (align_bits < size_bits)
        {
            size_bits = align_bits;
        }
        p = addr_to_page((void*)reg.start);
        p->size_bits = ARCH_ADDRESS_WIDTH_BITS;
        p->ref_cnt = 1;

        _pages_free(p, size_bits - ARCH_PAGE_SHIFT);
        reg.start += (1UL << size_bits);
    }
}

待完成...

posted @ 2022-09-04 23:54  Yanye  阅读(601)  评论(0编辑  收藏  举报