- 论坛徽章:
- 0
|
前期准备】
1、通常为了加快移植,使用Ramdisk作为root文件系统
2、基于MIPS的MPU,其实对于ARM,x86都是大同小异,不过小异有时候也需要注重的
3、移植开发环境:运行Linux的PC,包括MIPS等处理器的开发工具链,cross-binutils-as,ld,cross-gcc/gdb,特定目标平台的C开发库
4、内核源代码的获得 特定MPU的内核源码获得通常是来自特定处理器Linux版本的维护站点 如MIPS可到www.linux-mips.org中获得(其实里面包括了大多数的平台) 需要清楚如何组织内核源码,也就是了解诸如arch、kernel、mm、lib、platform、documentation、drivers、fs、include、init等目录含义
阅读源代码的好去处 http://joshua.raleigh.nc.us/docs/linux-2.4.10_html/
【起步】
Makefile的中arch应指明为MIPS
/usr/src/linux/arch/mips/boot 下面的Makefile文件中应有CROSS_COMPILE设为mipsel-linux、MIPS little-endian、 LOADADDR也必须被设定成MPU特有的内核镜像加载地址。
其他配置的需要修改文件中的/usr/src/linux/arch/mips/cinfig 如增加一个config选项CONFIG_MIPS,指定目标特殊平台代码 #make config编译内核为最低限度的系统,如串口驱动、ramdisk驱动、ext2fs #make dep安装依赖的相关程序 #make vmlinux生成镜像
[Ramdisk的生成] Ramdisk是一个包含ext2fs镜像的文件 CONFIG_BLK_DEV_INITRD=y CONFIG_BLK_DEV_RAM=y 指定了ramdisk对象可以将内核和压缩ramdisk镜像链接在一起 如果应用程序要在RAMDISK中运行,也可调整进去(方法尾述)
如果要单独生成ramdisk,可以编写工具脚本,当然linux-mips.org中已有人做好了。 ramdisk.o文件复制进/usr/arch/mips/boot/,链接入内核
下面是创建ramdisk较详细的文档
【深入】
针对特定处理器的内核代码与流行的代码之间差异是众所共知的。
下面是进入内核的伪代码
1、set big/little endian mode 2、clear the BEV bit 3、set the TLB bit 4、goto cpu_probe and return 5、set up stack for kernel 6、clear bss 7、goto prom_init and return 8、goto loadmmu and return 9、disable coprocessor 10、goto kernel start 11、goto 10
前6步是正确的设置寄存器 1、设置大端/小端字节模式 2、清除引导异常向量位,确定以后使用正确的异常向量 3、TLB的设置,不同mpu的tlb进入数是可能有差异的 4、cpu类型探测(简单实现的mips代码如下) LEAF(cpu_probe) la t3,mips_cputype li t2,MYCPU /* include/asm-mips/bootinfo.h */ b probe_done sw t2,(t3) END(cpu_probe) 注:bootinfo.h中有cputye(MYCPU)和机器组(MY_MACH_GROUP)的入口 ,mips_cputype变量需要cpu_probe函数来更新。使用此值来终止 异常处理和MMU程序。 5、初始化内核堆栈 6、清除内核映像的bss 7、将控制权交到init prom,刷新TLB和缓冲 8、载入mmu程序,loadmmu中内置缓冲处理函数 9、处理器0完成后禁止其他协处理器 10、跳转启动start_kernel()
由于TLB的进入数不同,处理器特定的include代码应增加CONFIG_MIPS配置选项 通常,需要修改的代码位于/usr/src/linux/arch/kernel/ 特定平台的TLB、缓冲处理程序包含于/usr/src/linux/arch/mips/mm/
【进一步深入】
内核代码的特定平台转换
特定平台需修改的代码包括中断处理、定时器、初始化、加载等程序。
在/usr/src/linux/include/asm/下建立mips目录,保存特定平台的include文件。
1、prom_init()
位于/usr/src/linux/arch/mips/your_platform/prom.c prom_init()函数可更改命令行字符串,来增加一些需要从引导程序传递给内核的参数。还可以设置机器组和可用内存上限。
如vr41xx
int __init prom_init(int argc, char **argv, char **envp) { unsigned long mem_limit, mem_detected, mem_use; unsigned long mem_start, mem_end, bootmap_size; int i;
// clear ERL and EXL in case the bootloader got us here through an // exception. this is mostly to make kernel debugger happy write_32bit_cp0_register(CP0_STATUS, 0);
// set upper limit to vr41xx maximum physical RAM (64MB) mem_limit = 64 << 20;
// the bootloader passes us argc/argv[], but the kernel wants one big string // put it in arcs_cmdline, which later gets copied to command_line // (see arch/mips/kernel/setup.c) // we skip the first argument which is the program file name strcpy(arcs_cmdline, ""); for (i = 1; i < argc; i++) { // is it the "mem=" option? if ( memcmp(argv[i], "mem=", 4) == 0 ) { // limit to amount of memory specified in megabytes mem_limit = simple_strtoul(argv[i] + 4, 0, 10) << 20;
printk("Command line argument limits memory to %dMB.\n", (int)mem_limit >> 20);
// don’t pass this arg on to the kernel continue; }
if ( strlen(arcs_cmdline) > 0 ) strcat(arcs_cmdline, " "); strcat(arcs_cmdline, argv[i]); }
mips_machgroup = MACH_GROUP_VR41XX;
........
2、start_kernel()
位于/usr/src/linux/init/main.c
只抄出函数头
asmlinkage void __init start_kernel(void) { char * command_line; unsigned long mempages; extern char saved_command_line[]; /* * Interrupts are still disabled. Do necessary setups, then * enable them */ lock_kernel(); printk(linux_banner); setup_arch(&command_line,&memery_start,&memery_end); memery_start=paging_init(memery_start,memery_end); parse_options(command_line); trap_init(); init_IRQ(); sched_init(); time_init(); softirq_init();
...
3、体系安装函数setup_arch()
位于/usr/src/linux/arch/mips/kernel/setup.c 功能:调用特殊平台的体系安装函数;将命令行字符串、memery_start、memery_end传递给该函数的调用者;更新链接镜像的开始、结束地址。
__initfunc(void setup_arch(char **cmdline_p, unsigned long * memory_start_p, unsigned long * memory_end_p)) { #ifdef CONFIG_BLK_DEV_INITRD #if CONFIG_BLK_DEV_INITRD_OFILE extern void *__rd_start, *__rd_end; #endif #endif
myplatform_setup(); strncpy(command_line, arcs_cmdline, CL_SIZE); *cmdline_p = command_line;
*memory_start_p = (unsigned_long) &_end; *memory_end_p = mips_memory_upper;
#ifdef CONFIG_BLK_DEV_INITRD #if CONFIG_BLK_DEV_INITRD_OFILE // Use the linked-in ramdisk // image located at __rd_start. initrd_start = (unsigned long)&__rd_start; initrd_end = (unsigned long)&__rd_end; initrd_below_start_ok = 1; if (initrd_end > memory_end) { printk(*initrd extends beyond end of memory * *(0x%08lx > 0x%08lx)\ndisabling initrd\n*, initrd_end, memory_end); initrd_start = 0; } #endif #endif }
尾述: #gzip ramdisk.img.gz #mkdir tempmnt #mount -o loop ramdisk.img.gz tempmnt #cp -f yourprogram tempmnt/bin #umount tempmnt #gzip ramdisk.img #cp -f ramdisk.img.gz /待烧文件存放目录
、特定平台初始化代码
/usr/src/linux/arch/mips/your_platform/setup.c 包括各种基地址、特定平台的RTC和PCI操作
__initfunc(void myplatform_setup(void)) {
irq_setup = myplatform_irq_setup; /* * mips_io_port_base is the beginning *of the address space to which x86 * style I/O ports are mapped. */
mips_io_port_base = 0xa0000000;
/* * platform_io_mem_base is the beginning of I/O bus memory space as * seen from the physical address bus. This may or may not be ident- * ical to mips_io_port_base, e.g. the former could point to the
beginning of PCI *memory space while the latter might indicate PCI I/O * space. The two values are used in different sets of macros. This * must be set to a correct value by the platform setup code. */
platform_io_mem_base=0x10000000;
/* * platform_mem_iobus_base is the beginning of main memory as seen * from the I/O bus, and must be set by the platform setup code. */
platform_mem_iobus_base=0x0;
#ifdef CONFIG_REMOTE_DEBUG
/* * Do the minimum necessary to set up debugging */
myplatform_kgdb_hook(0); remote_debug = 1; #endif
#ifdef CONFIG_BLK_DEV_IDE ide_ops = &std_ide_ops; #endif
#ifdef CONFIG_VT #if defined(CONFIG_DUMMY_CONSOLE) conswitchp = &dummy_con; #endif #endif
/* * just set rtc_ops && pci_ops; forget the rest */
rtc_ops = &myplatform_rtc_ops; pci_ops = &myplatform_pci_ops; }
myplatform_pcibios_fixup() myplatform_pcibios_read_config_byte() myplatform_pcibios_read_config_word() myplatform_pcibios_read_config_dword() myplatform_pcibios_write_config_byte() myplatform_pcibios_write_config_word() myplatform_pcibios_write_config_dword()
5、中断处理
/usr/src/linux/arch/mips/your_platform/irq.c /usr/src/linux/arch/mips/your_platform/inthander.S
trap_init()函数复制KSEG0向量位置的顶级异常处理程序。 一、 使用set_except_vector()来安装最高优先级的中断处理器。 二、初始化应用平台上的中断控制器。
在激活远程调试程序时,调用set_debug_traps(),就能将调试信息阻止或报告给调试程
序。
static void __init myplatform_irq_setup (void) { set_except_vector (0, myplatform_handle_int);
// Initialize InterruptController InterruptController_Init(IsrTable);
#ifdef CONFIG_REMOTE_DEBUG
printk ("getting debug traps - please connect the remote debugger.\n");
set_debug_traps ();
breakpoint ();
#endif
}
6、顶级中断处理程序
顶级中断处理器首先保存所有寄存器,然后禁止级别低的中断,并通过CAUSE寄存器来
查找中断源。 1、如果是定时器中断,调用对应的ISR 2、如果不是,检查是否是同中断控制器相连的连接线路发生中断。
NESTED(myplatform_handle_int, PT_SIZE, ra) .set noat SAVE_ALL CLI .set at mfc0 s0, CP0_CAUSE # get irq mask
/* First, we check for counter/timer IRQ. */ andi a0, s0, CAUSEF_IP5 beq a0, zero, 1f andi a0, s0, CAUSEF_IP2 # delay slot, check hw0 interrupt /* Wheee, a timer interrupt. */ move a0, sp jal timer_interrupt nop # delay slot j ret_from_irq nop # delay slot
1: beq a0, zero, 1f nop /* Wheee, combined hardware level zero interrupt. */ jal InterruptController_InterruptHandler move a0, sp # delay slot j ret_from_irq nop # delay slot
2:
/* Here by mistake? This is possible, *what can happen is that by the time
we *take the exception the IRQ pin goes low, so *just leave if this
is the case. */ j ret_from_irq nop END(myplatform_handle_int)
中断控制的中断处理程序
其获取引起中断的中断向量,然后执行中断源处理程序
void InterruptController_InterruptHandler (struct pt_regs *regs)
{
IntVector intvector; struct irqaction *action; int irq, cpu = smp_processor_id(); InterruptControllerGetPendingIntVector(&intvector); InterruptControllerGetPendingIntSrc((&irq); action = (struct irqaction *)intvector;
if ( action == NULL )
{ printk(*No handler for hw0 irq: %i\n*, irq); return; }
hardirq_enter(cpu); action->handler(irq, action->dev_id,regs); kstat.irqs[0][irq]++;hardirq_exit(cpu); } // InterruptController_InterruptHandler ()
诸如以下函数: request_irq() /*给给定的中断源安装中断处理程序*/ ;
free_irq() /*释放分配给已定中断的内存*/ ;
enable_irq() /*调用中断控制函数使给定中断链有效*/ ;
disable_irq() /*使定义中断链失效*/
目标平台中这些函数是必不可少的。
7、定时中断
/usr/src/linux/arch/mips/your_platform/time.c
包括特定平台的定时器代码。如mips上的linux内核要求有100hz的定时中断。如用处理
器0的定时器编程来产生100hz的中断。 计数寄存器和比较寄存器一起构成定时器。 激活时,计数寄存器包含一个自由运行的计数器,伴随每个处理器的时钟频率寄存器的
计数值也不断的变化。当计数寄存器中的赋值与比较寄存器匹配时,寄存器被复位,同
时产生外部硬件中断。 定时中断服务例程(ISR)调用do_timer()。 而通过用写比较寄存器的方法可以清除定时器的中断。
8、串口控制驱动
将控制台运行于串口驱动之上。经过测试的串口驱动可以被printk()内核调试函数调用
。 最简单的串口驱动需要提供以下函数: 1、serial_console_init()-- 在正确初始化控制驱动之前,用于注册内核printk()函
数的控制打印程序。
2、serial_console_setup()-- 初始化串口。
3、serial_console_write(struct console *console,const char *string, int
count)-- 用于写count字符
9、tty驱动
被注册为tty的串口驱动所产出的中断可以用来创建终端设备。
/usr/src/linux/driver/char
选择其中与自己串口硬件最匹配的,适当加以修改即可。详细的可以参看LDD
make config时将CONFIG_SERIAL选为Y。
10、引导器的选择
基于各种平台的引导器开发是一个比较费事费力的工作。通常状况下,选择LILO。
LILO是先将引导的command_line(eg. root=/dev/ram)参数传递给内核进行解析,然后
调用kernel_entry交出控制权走人。这个kernel_entry既是内核的入口地址。
在调试时,如果loader有自己的print函数,调试将变的简单。内核中的printk()缓冲
了控制台的所有输出,直至该控制台console_init()[/usr/src/linux/init/mian.c中]
被初始化为止。
11、添加自己的驱动、模块
insmod rmmod
12、调试
为了便于调试,printk()使用的最好频繁一些。
进行远程gdb调试时,需要在编译内核时,将CONFIG_REMOTE_DEBUG设为Y。 注意一下putDebugChar (char ch) 和 getDebugChar()这两个实现gdb通过串口进行远
程调试的函数。
/usr/src/linux/include/linux/tty_ldisc.h中指定的
当然针对并口远程调试,则需要在putDebugChar()中设置高比特位来实现多路传输。 注.../driver/net/plip.c中有相关字符收发代码。 |
|