前期准备】
1、通常为了加快移植,使用Ramdisk作为root文件系统
2、基于MIPS的MPU,其实对于ARM,x86都是大同小异,不过小异有时候也需要注重的
3、移植开发环境:运行Linux的PC,包括MIPS等处理器的开发工具链,cross-binutils-as,ld,cross-gcc/gdb,特定目标平台的C开发库
4、内核源代码的获得
特定MPU的内核源码获得通常是来自特定处理器Linux版本的维护站点
如MIPS可到中获得(其实里面包括了大多数的平台)
需要清楚如何组织内核源码,也就是了解诸如arch、kernel、mm、lib、platform、documentation、drivers、fs、include、init等目录含义
阅读源代码的好去处
【起步】
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中有相关字符收发代码。
阅读(1122) | 评论(0) | 转发(0) |