Linux 移植流水账

Linux 移植流水账

首先,要看芯片的核Linux是否支持,如果不支持,那么工作量极其大,放弃吧。
如果cpu核linux支持的话,移植linux,主要是根据SOC所集成的外围模块,将驱动完成。
下面以一款ARM926ejs的芯片为例,在linux 26.22.5上的移植 。
假设芯片叫 MYCHIP
第一步 环境的建立 :
1 在config文件中添加你的芯片支持
a) mkdir arch/arm/mach_mychip, 添加Kconfig和Makefile文件
修改arch/arm/Kconfig :
menu "System Type"
choice
prompt "ARM system type"
default ARCH_MYCHIP
config ARCH_MYCHIP
bool "my chip "
select ARM_AMBA
help
This enables support for mychip
-------
source "arch/arm/mach-mychip/Kconfig"
这样 make menuconfig ARCH=arm 就出现了你的芯片 。
b) 在arch/arm/mm/Kconfig 中
# ARM926T
config CPU_ARM926T
depends on ---| | ARCH_MYCHIP
deafult if ............. | | ARCH_MYCHIP
这样就申明你的芯片属于 arm 926
c) 在 include/asm-arm/下创建一个目录 mach-mychip
然后修改arch/arm/Makefile 加上 :
machine-$(CONFIG_ARCH_MYCHIP) := mychip
这样当选择上你的芯片时,编译的时候 会将 include/asm-arm/.arch 链接到
include/asm-arm/arch-mychip
2 加上cpu的配置
a) 在arch/arm/tools中,mach_types加上芯片ID
b) 配置cpu的起始物理地址 和时钟频率
编译
将提示asm/arch/memory.h,asm/arch/timex.h这两个文件没有。
创建include/asm-arm/arch-mychip/memory.h
#define PHYS_OFFSET UL(0x60000000)
这个是根据芯片SDRAM的 地址空间,比如我现在所用的芯片它的SDRAM地址空间为:6000 0000 – 67FF FFFF 128M
#define __virt_to_bus(x) __virt_to_phys(x)
#define __bus_to_virt(x) __phys_to_virt(x)
这两个是:Virtual view <-> DMA view memory address translations
创建include/asm-arm/arch-mychip/timex.h
#define CLOCK_TICK_RATE (50000000 / 16)
linux用宏 CLOCK_TICK_RATE来表示输入时钟脉冲的频率.
在linux中,1秒中时钟中断的次数 以HZ表示,对于arm #define HZ 100 也就是10ms来一次时钟中断,
Linux用宏LATCH来定义要写到计数器中的值,隔多少个时钟周期产生一次时钟中断。显然LATCH应该由下列公式计算:
LATCH=(1秒之内的时钟周期个数)÷(1秒之内的时钟中断次数)=(CLOCK_TICK_RATE)÷(HZ)
c) 配置vmalloc 虚拟地址分配
编译
提示 缺少 asm/arch/vmalloc.h:
创建include/asm-arm/arch-mychip/vmalloc.h
#define VMALLOC_END (PAGE_OFFSET + 0x18000000)
PAGE_OFFSET 启始的虚拟地址的分配, linux分为内核空间和用户空间,内核空间为3G-4G,所以PAGE_OFFSET定义为 0xC0000000
VMALLOC_END:
(转http://www.lslnet.com/linux/f/docs1/i34/big5260475.htm)
vmalloc(見mm/vmalloc.c文件)的目的是供內核分配在虛擬空間必須是連續的大塊內存(物理地址不要求連續),其所佔用的地址範圍是特定於平台的常數VMALLOC_START和VMALLOC_END定義的
(转http://idcnews.net/html/edu/20080101/281406.html)
Linux用vm_struct结构来表示vmalloc使用的线性地址.vmalloc所使用的线性地址区间为: VMALLOC_START VMALLOC_END.借用>中的一副插图,如下示:

d) 配置中断,IO ,DMA
编译 inux移植流水帐
将提示缺少 :asm/arch/irqs.h,asm/arch/dma.h,asm/arch/io.h这三个文件
创建irqs.h,在这个文件中 主要是定义NR_IRQS ,以及定义中断的宏,比如定义
#define I2C_INT 39
标记I2C中断为39号 ,以后在程序里 好用
request_irq( I2C_INT......)
创建 include/asm-arm/arch-dw/io.h,这个文件主要定义IO地址到内存地址的转换
创建 include/asm-arm/arch-dw/dma.h,这个文件主要DMA方面的一些定义
e) 查询中断号的宏实现
这个和特定的CPU有关,就是通过查询中断状态器,得知现在是那个中断
编译
提示缺少include/asm-arm/arch-mychip/entry-macro.S
这个文件主要是low level IRQ helper 宏 get_irqnr_and_base,该宏获取irq中断号,存储到r0寄存器中,作为参数传递给asm_do_IRQ
加上下面两个宏:
.macro get_irqnr_preamble, base, tmp
.endm
.macro arch_ret_to_user, tmp1, tmp2
.endm
f)hardware.h
编译
提示缺少include/asm-arm/arch-mychip/hardware.h
g) 设置系统idle ,reset函数
编译 提示缺少asm/arch/system.h:
这个文件主要实现 arch_idle 和arch_reset 2个函数
arch_reset 根据芯片,按次序设置一些寄存器 ,让系统重启
3
a)上面的步骤做好后,将驱动全部去掉,编译,将提示 arch/arm/arch-mychip/built_in.o找不到,
在arch/arm/arch-mychip中随便添加一个mychip.c文件 ,修改makefile
obj-y := mychip.c
b) 编译工具检查
编译 出现
no machine record defined
原来在arch/arm/kernel/vmlinux.lds末尾 :
ASSERT((__proc_info_end - __proc_info_begin), "missing CPU support")
ASSERT((__arch_info_end - __arch_info_begin), "no machine record defined")
这表明我们的编译工具太老了,一看,编译工具是arm-2006q3, 2006第3季度的 ,确实太老了。
升级到arm-2008q3,编译还是出现这个问题。
我们编译出错,因为没有arch.info.init这个区,那么这个区在哪里定义的呢? 在linux/include/asm-arm/mach/arch.h 中发现MACHINE_START宏定义:
#define MACHINE_START(_type,_name) \
static const struct machine_desc __mach_desc_##_type \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_##_type, \
.name = _name,
#define MACHINE_END \
};
因此 在mychip.c中 加上 __enable_mmu
MACHINE_START(XXXX, "ARM-DW")
/* Maintainer: ARM Ltd/Deep Blue Solutions Ltd */
MACHINE_END
XXXX根据arch/arm/tools/mach-types
第二步 :启动kernel
1 将uImage 启动后,
在打印
Start Kernel ....
后出现
undefined instruction
Start Kernel .... 是在uboot /lib_arm/armlinux.c 的 do_bootm_linux函数中 ,开始启动kernel时打印的
那后面呢???
2 在uboot中对 SDRAM和串口已经初始化了,但在Linux中也有串口的驱动,串口驱动初始化后,就可以用printk打印东西了,在之前,怎么打印呢?需要实现putc函数 .
在include/asm-arm/arch-mychip中,添加uncompress.h,实现putc,flush函数 。
3 原来是缺少 Makefile.boot文件
在arch/arm/boot/Makefile中,会include $(srctree)/$(MACHINE)/Makefile.boot
创建arch/arm/mach-mychip/Makefile.boot
该文件主要定义
zreladdr-y 0x60008000 内核解压缩后的执行地址 (0x60000000 SDRAM基地址+0x00008000)
params_phys-y 0x60000100 内核参数物理地址 (0x60000000 SDRAM基地址 +0x00000100)
initrd_phys-y initrd物理地址
4 还是不行 ,用一个可以启动的uImage 进行对比
好的:
## Booting image at 62000000 ...
Image Name: Kernel Image
Created: 2009-03-18 6:23:44 UTC
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 1452480 Bytes = 1.4 MB
Load Address: 60008000
Entry Point: 60008000
出现undefined instrution 的 uImage:
## Booting image at 62000000 ...
Image Name: Kernel Image
Created: 2009-03-20 9:42:06 UTC
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 2217548 Bytes = 2.1 MB
Load Address: 30008000
Entry Point: 30008000
两者的load Address ,entry point不一样,
这是在uboot 的 common/cmd_bootm.c 的 do_bootm函数中 ,调用
print_image_hdr ((image_header_t *)addr);
uImage的 头为 一个image_hear_t结构 。
Load Address 应该在Makefile.boot中定义为zreladdr-y 60008000啊 怎么变成 30008000了呢 ?而且大小也不对阿,怎么2.1 M
后来终于发现,我用的是./uImage 而不是arch/arm/boot/uImage ,而./uImage为非压缩内核,在./Makefile中,用的是 :
$(CROSS_COMPILE)objcopy -O binary -R .note -R .comment -S vmlinux linux.bin
mkimage -A $(ARCH) -O linux -T kernel -C none -a 30008000 -e 30008000 -n "Kernel Image Giant" -d linux.bin uImage
,需要修改成:
$(CROSS_COMPILE)objcopy -O binary -R .note -R .comment -S vmlinux linux.bin
mkimage -A $(ARCH) -O linux -T kernel -C none -a 60008000 -e60008000 -n "Kernel Image Giant" -d linux.bin uImage,
换成arch/arm/boot/uImag后
## Booting image at 62000000 ...
Image Name: Linux-2.6.22.5
Created: 2009-03-20 10:13:20 UTC
Image Type: ARM Linux Kernel Image (uncompressed) //mkimage 没有再压缩zImage
Data Size: 1108164 Bytes = 1.1 MB
Load Address: 60008000
Entry Point: 60008000
OK
Start Kernel ....
怎么后面没有了呢 ??

怎么自动就生成了uncompressed uImage呢?

一开始怎么也不理解,应该是compressed uImage啊? 默认的应该是生成compressed uImage .
原来,在include/asm-arm/mach-mychip/uncompress.h中 ,用的是串口1,而我们接的是串口2,修改这个文件,让串口2输出。
在Start Kernel .... 后出现
Uncompressing Linux....................................................................... done, booting the kernel.
说明kernel是压缩的,并正确解压了。
如果用非压缩内核 ,则改为./uImage ,下载后:
## Booting image at 62000000 ...
Image Name: Kernel Image Giant
Created: 2009-03-23 5:42:39 UTC
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 2217548 Bytes = 2.1 MB
Load Address: 60008000
Entry Point: 60008000
OK
5 解压完内核后,或者下载非压缩内核后,
由于在start_kernel一开始就调用printk, 但其实并不是立即就输出,而是放在一个buffer中,__log_buf

直到register_console调用后,register_console注册完后调用release_console_sem(),release_console_sem-->call_console_drivers打印缓冲区里的东西.

也就是说在调用register_console之前的printk,并不打印,而是放在buffer中,在注册console时,把之前打印的立即发送出去.

register_console之后的printk可以立即打印数据.

那么在哪里会调用register_console呢?

uart_add_one_port ->register_console
因此在uart驱动没完成之前,在misc.c中,有一个函数putstr,他直接调用putc ,然后调用flush .

要么在start_kernel中,也这样来试试,看看问题出在哪里 ?
在start_kernel的一开始,用putc('c');flush(); 没输出,那说明在调用start_kernel之前就出错了.
在/init/main.c中 加入:
#include
asmlinkage void __init lawrence_debug(void)
{
putc('a');
putc('b');
putc('\n');
flush();
}
用这个函数来看到底在哪里出问题了(asmlinkage一定要,否则在汇编中调用不了)。
然后在./arch/arm/kernel/head.S中,加入 bl lawrence_debug , 看程序跑到哪里。
发现,原来是 在 __lookup_machine_type后死机了,进入了__error_a,也就是没找到machine_type ,
在head-Common.S中,__lookup_machine_type函数的输入参数是r1 (machine architecture number),而这个参数是 uboot中传入的。

为了更好的看到底问题出在哪里,可以打开CONFIG_DEBUG_LL宏来看.
http://blog.csdn.net/aaronychen/archive/2008/08/27/2838341.aspx
好像__lookup_machine_type的r1不对,修改为:
__lookup_machine_type:
mov r1 ,#0x32
orr r1 ,r1,#0x400
就可以找到machine 了,怎么uboot就没传过来呢??
6 打开CONFIG_DEBUG_LL宏,提示缺少debug-macro.S,
这个文件主要是为了 printascii等函数用 ,它定义了4个宏
addruart : 得到UART的地址,看芯片UARAT1或者2 的地址,这里有个问题,MMU如果没打开,那地址好办,
MMU打开了呢?则需要提供虚拟地址,IO的虚拟地址通过下面的方法:
在 MACHINE_START中定义了.map_io = mychip_map_io
mychip_map_io -> iotable_init (map_desc ...) ,map_desc结构中定义了IO地址和虚拟地址的对应 关系 ,
在/init/main.c的start_kernel -->startup_arch( arch/arm/kernel/setup.c) -->paging_init(在arch/arm/mm/init.c 中)-->devicemaps_init -->map_io
senduart :发送内容
waituart : 等待
busyuart : 判断是否busy , busy 的话循环
测试这个文件很简单,在head.S中 bl __error_a 看看有没有打印出来
7 发现 :
ldr r13, __switch_data @ address to jump to after
@ mmu has been enabled
adr lr, __enable_mmu @ return (PIC) address
------走到此处
add pc, r10, #PROCINFO_INITFUNC
r10 为 procinfo的基地址, 因此 add pc ,r10 ... 将执行 __cpu_flush 也就是 __arm926_setup(arch/arm/mm/proc_arm926.S) , 该函数最后 mov pc ,lr 将调用 __enable_mmu
__enable_mmu --> __turn_mmu_on -> mov pc ,r13 将 调用 __switch_data
而在 __map_switched 下面调用 bl __error_a 没反应
在__turn_mmu_on函数中 ,
__turn_mmu_on:
mov r0, r0
此处用 bl __error_a 有打印
mcr p15, 0, r0, c1, c0, 0 @ write control reg
mrc p15, 0, r3, c0, c0, 0 @ read id reg
mov r3, r3
mov r3, r3
此处用 bl __error_a 没有打印
mov pc, r13
这说明 ,在 MMU起效后 , addruart将用虚拟地址 ,而此时IO的虚拟地址是多少呢 ??
这个要根据
.phys_io = 0x05C00000, // UART2 phy addr
.io_pg_offst = ((0xf0000000) >> 18) & 0xfffc, //UART2 virt addr
将5c00000 映射到 0xf0000000
修改 debug-macro.S
.macro addruart,rx
mrc p15, 0, \rx, c1, c0
tst \rx, #1 @ MMU enabled?
moveq \rx, #0x05C00000 @ physical
movne \rx, #0xf0000000 @ virtual
.endm
这样就可以打印出来了 。
8 在main.c start_kernel前定义 :
#define UART2_TX_FIFO_LEVELV (*(volatile unsigned char *)0xf0000020)
#define UART2_TX_DATAV (*(volatile unsigned char *)0xf0000028)
static inline void putcv(int c)
{
//wait until there is space in TX FIFO
while(UART2_TX_FIFO_LEVELV == 0x80)
barrier();
UART2_TX_DATAV = c;
}
static inline void flushv(void)
{
while(UART2_TX_FIFO_LEVELV)
barrier();
}
asmlinkage void __init lawrence_debug(void)
{
putcv('a');
putcv('b');
putcv('\n');
flushv();
}
asmlinkage void __init start_kernel(void)
{
char * command_line;
extern struct kernel_param __start___param[], __stop___param[];
lawrence_debug();
.....
发现打印出来了 。
至此 start_kernel终于运行了。

第三步:系统初始化
1 系统启动流程大致为:
start_kernel :
setup_arch ....
init_IRQ -->init_arch_irq
...
timer_init -->system_timer ...
... rest_init -->将调用init call
在 MACHINE_START里面有 :
.map_io = mychip_map_io,
.init_irq = mychip_init_irq,
.timer = &mychip_timer,
.init_machine = mychip_init,
在/init/main.c的start_kernel -->setup_arch( arch/arm/kernel/setup.c) -->paging_init(在arch/arm/mm/init.c 中)-->devicemaps_init -->map_io, mychip_map_io -> iotable_init (map_desc ...) ,map_desc结构中定义了IO地址和虚拟地址的对应 关系 ,
我们用一个宏 IO_ADDRESS (include/asm/arch/hardware.h)
:#define IO_ADDRESS(x) (((x) & 0x0fffffff) + (((x) >> 4) & 0x0f000000) + 0xf0000000)
将 x ---> 0xfx ( x 在0x00000000 ~ 0x0f000000 )
x ---> 0xf(x+1~f) ( x 在0x10000000 ~ 0xf0000000 )
我们芯片的IO地址都在0x00000000 ~ 0x0f00000000 之间 ,所以物理地址变成虚拟地址只是加上了0xf0000000,变成高位地址 。
最终 映射关系为 :
其他的IO --> 0xF0000000 (包括 NAND Flash controller ,TDM ....)
SRAM --->0xD0000000
SDRAM ---> 0xC0000000
NOR Flash没有映射 ,只能通过 mtdblock0 ,mtdblock1来访问 ?????
修改
.phys_io = 0x05C00000, // UART2 phy addr
.io_pg_offst = ((IO_ADDRESS(0x5C00000)) >> 18) & 0xfffc, //UART2 virt addr
将5c00000 映射到 0xf5C00000
修改 debug-macro.S
.macro addruart,rx
mrc p15, 0, \rx, c1, c0
tst \rx, #1 @ MMU enabled?
moveq \rx, #0x00000000 @ physical
movne \rx, #0xf0000000 @ virtual
orr \rx, \rx, #0x05C00000 @ UART2 offset
.endm
这样就对应起来 。 可以在任何时候都用lawrence_debug看系统跑到哪里了。
在main.c start_kernel前定义 :
#define UART2_TX_FIFO_LEVELV (*(volatile unsigned char *)0xf5C00020)
#define UART2_TX_DATAV (*(volatile unsigned char *)0xf5C00028)
setup_arch的末尾将:
init_arch_irq = mdesc->init_irq;
system_timer = mdesc->timer;
init_machine = mdesc->init_machine;
在arch/arm/kernel/setup.c中
customize_machine --> init_machine
arch_initcall(customize_machine);
对于 init_irq ,主要是结构体 static struct irq_chip , 并将set_irq_handler(i, handle_level_irq);
并且 注意 在低版本的SA_INTERRUPT 替换成 IRQF_DISABLED
SA_TIMER 替换成 IRQF_TIMER
set_irq_chipdata 替换成 set_irq_chip_data
而 timer 主要是提供系统时钟(tick),这样,每到一个tick,发生任务切换。

第四步: 添加console驱动
console驱动就是输出printk的信息 。 这是第一步,这样在程序中可以用printk来打印,看执行到哪里。
对于 驱动的添加,利用platform driver model , 现在 arch init 中,用amba_device_register加入device ,
然后在module_init中 用 amba_driver_register注册驱动,在注册驱动的过程中,会根据amba_driver中的idtable (包括id和mask)来和device中的periphid 比较 ,看能否match ,能的话就调用probe函数 。
对于 嵌入式设备,console是用输出到uart ,所以只要在uart的驱动中定义struct console mychip_console
然后在struct uart_driver mychip_reg中的.cons = &mychip_console ,这样在probe函数中加上
uart_add_one_port(&mychip_reg,....) 就会把console注册上了。
添加好后,启动kernel
打印:
Starting kernel ...
Linux version 2.6.22.5 (lawrencekang@lawrencekang) (gcc version 4.3.2 (Sourcery G++ Lite 2008q3-72) ) #48 Wed Apr 8 17:40:52 CST 2009
CPU: ARM926EJ-S [41069265] revision 5 (ARMv5TEJ), cr=00053177
Machine: ARM-MYCHIP
Memory policy: ECC disabled, Data cache writeback
CPU0: D VIVT write-back cache
CPU0: I cache: 16384 bytes, associativity 4, 32 byte lines, 128 sets
CPU0: D cache: 16384 bytes, associativity 4, 32 byte lines, 128 sets
Built 1 zonelists. Total pages: 16256
Kernel command line: root=/dev/mtdblock2 rootfstype=yaffs2 console=ttymychip0 mem=64M mtdparts=dwnand:3m(kernel),3m(splash),-(rootfs)
2
PID hash table entries: 256 (order: 8, 1024 bytes)
Console: colour dummy device 80x30
Dentry cache hash table entries: 8192 (order: 3, 32768 bytes)
Inode-cache hash table entries: 4096 (order: 2, 16384 bytes)
Memory: 64MB = 64MB total
Memory: 62616KB available (1992K code, 155K data, 60K init)
Mount-cache hash table entries: 512
CPU: Testing write buffer coherency: ok
NTFS driver 2.1.28 [Flags: R/W].
io scheduler noop registered (default)
Serial: MYCHIP UART driver
mychipb:1: ttyMYCHIP0 at MMIO 0x5c00000 (irq = 7) is a MYCHIP_UART
Advanced Linux Sound Architecture Driver Version 1.0.14 (Thu May 31 09:03:25 2007 UTC).
ALSA device list:
No soundcards found.
drivers/rtc/hctosys.c: unable to open rtc device (rtc0)
VFS: Cannot open root device "mtdblock2" or unknown-block(0,0)
Please append a correct "root=" boot option; here are the available partitions:
Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
第五步: 添加网卡驱动,以支持NFS
从上面的输出信息可以看到,我们的下一步需要挂文件系统了。
在开发阶段,最好用NFS,这需要添加网卡驱动。
需要注意的是sk_buffer ,在老的版本中(比如2.6.17) 有mac,h,nh,而在新的版本中变成了 mac_header ,network_header等。
加好网卡驱动,并在文件系统中选择上NFS支持(以及NFS 根文件系统),编译后 :
.......
TCP cubic registered
NET: Registered protocol family 1
NET: Registered protocol family 17
drivers/rtc/hctosys.c: unable to open rtc device (rtc0)
IP-Config: Unable to set interface netmask (-22).
Looking up port of RPC 100003/2 on 172.22.1.35
Looking up port of RPC 100005/1 on 172.22.1.35
VFS: Mounted root (nfs filesystem).
Freeing init memory: 76K
Warning: unable to open an initial console.
Kernel panic - not syncing: No init found. Try passing init= option to kernel.
第六步: 根文件系统的制作
利用busybox 等制作好rootfs,修改启动脚本,也就是在/etc/init.d/rcS中, 加入 echo "hello" ,其他什么也不作, 在/etc/inittab中,加上:ttyMYCHIP0::respawn:-/bin/sh ,其中ttyMYCHIP 在uart的驱动中定义了。
进入后,就可以 用 ls , cp , ifconfig等命令了 。
第七步: SOC驱动的添加,
1) I2C 驱动

2) SPI 驱动

3)LCD 驱动

4) audio 驱动 ,这个在低的版本中没有 asoc , 在2.6.22中有asoc

5) Flash驱动 以及 Local File System (Yaffs2 移植 )
6)

3)

在linux kernel的文件夹中

/arch 加了一个mach_XXX
/block unchanged
/Documentation 不用理会
/driver 将 mmc_spi方式移植过来了
驱动中
dwipc
wifi驱动
USB
I2C
SPI
UART
ALSA
NAND
LCD
中断???

/fs 文件系统:添加了squashFS
/init : do_mounts_rd.c因为加了squashFS做了相应修改
/ipc : unchanged
/kernel : sched.c 添加了 DPM
/lib : add crc7.c crc-itu-t.c这些都是因为驱动中加了 mmc_spi
/mm unchanged
/net 很奇怪,这里也有5个文件修改了
/scripts :unchanged
/security unchanged
/sound 添加了2个设备的驱动
/usr unchanged

Makefile 修改了编译器等

其他都没变

你可能感兴趣的:(C++,c,linux,cache,C#)