Chinaunix首页 | 论坛 | 博客
  • 博客访问: 52654
  • 博文数量: 20
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 22
  • 用 户 组: 普通用户
  • 注册时间: 2015-09-16 01:37
个人简介

Nothing

文章分类

全部博文(20)

文章存档

2015年(20)

我的朋友

分类: 嵌入式

2015-11-13 12:19:18

原文地址:Linux的嵌入式移植 作者:myfaxmail

前期准备】 

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