1. 程式人生 > >第4章 核心移植 C(Exynos4412+Linux3.14)

第4章 核心移植 C(Exynos4412+Linux3.14)

4.6核心的配置和編譯

【Linux核心下載地址】
1、 解壓核心
將linux-3.14.tar.xz拷貝到/home/linux下並解壓
$tar xvf linux-3.14.tar.xz
$ cd linux-3.14
2、 修改核心頂層目錄下的Makefile
$ vim Makefile
修改:
這裡寫圖片描述
為:

ARCH        ?= arm
CROSS_COMPILE   ?= arm-none-linux-gnueabi-

3、 匯入預設配置
$ make exynos_defconfig
4、 配置核心
$ make menuconfig
System Type —>
(2) S3C UART to use for low-level messages
該命令執行時會彈出一個選單,我們可以對核心進行詳細的配置。這裡我們先檢視一下,核心都提供了那些功能!
5、 編譯核心


$ make uImage
通過上述操作我們能夠在arch/arm/boot目錄下生成一個uImage檔案,這就是經過壓縮的核心映象。
如果編譯過程中提示缺少mkimage工具,需將第二天編譯的uboot原始碼中的tools/mkimage拷貝到ubuntu的/usr/bin目錄下
$ cp u-boot-2013.01/tools/mkimage /usr/bin
修改裝置樹檔案
生成裝置樹檔案,以參考板origen的裝置數檔案為參考。
$ cp arch/arm/boot/dts/exynos4412-origen.dts arch/arm/boot/dts/exynos4412-lckj4412.dts
新增新檔案需修改Makefile才能編譯
$
vim arch/arm/boot/dts/Makefile

這裡寫圖片描述
下新增如下內容
exynos4412-lckj4412.dtb \
6、 裝置樹檔案
$ make dtbs
7、 拷貝核心和裝置樹檔案到/tftpboot目錄下
$ cp arm/arm/boot/uImage /tftpboot
$ cp arch/arm/boot/dts/exynos4412-lckj4412.dtb /tftpboot/

4.7網絡卡驅動的移植

1、 裝置樹檔案修改:
$ vim arch/arm/boot/dts/exynos4412-lckj4412.dts
新增如下內容:

srom-cs1@5000000 {
        compatible = "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;
        reg = <0x5000000 0x1000000>;
        ranges;

        [email protected]5000000 {
            compatible = "davicom,dm9000";
            reg = <0x5000000 0x2 0x5000004 0x2>;
            interrupt-parent = <&gpx0>;
            interrupts = <6 4>;
            davicom,no-eeprom;
            mac-address = [00 0a 2d a6 55 a2];
        };
    };

2、 修改檔案drivers/clk/clk.c
修改
這裡寫圖片描述

static bool clk_ignore_unused = true;

3、 配置核心:
make menuconfig
[*] Networking support —>
Networking options —>
<*> Packet socket
<*>Unix domain sockets
[*] TCP/IP networking
[*] IP: kernel level autoconfiguration
Device Drivers —>
[*] Network device support —>
[*] Ethernet driver support (NEW) —>
<*> DM9000 support
File systems —>
[*] Network File Systems (NEW) —>
<*> NFS client support
[*] NFS client support for NFS version 3
[*] NFS client support for the NFSv3 ACL protocol extension
[*] Root file system on NFS
4、 編譯核心和裝置樹
$ make uImage
$ make dtbs
5、 測試:
拷貝核心和裝置樹檔案到/tftpboot目錄下
$ cp arm/arm/boot/uImage /tftpboot
$ cp arch/arm/boot/dts/exynos4412-lckj4412.dtb /tftpboot/
啟動開發板,修改核心啟動引數,通過NFS方式掛載根檔案系統

4.8 LED驅動的移植

1. 新增驅動檔案
將實驗程式碼lckj4412_led_kernel /lckj4412_led_drv.c拷貝到drivers/char下

2. 修改drivers/char/Kconfig
在menu “Character devices”下面
新增如下內容:
config LCKJ4412_LED
tristate “LCKJ4412LED Device Support”
depends onARCH_EXYNOS4
help
support leddevice on LCKJ4412develop board

3. 修改drivers/char/Makefile
在檔案最後新增如下程式碼
obj-$(CONFIG_LCKJ4412_LED) += lckj4412_led_drv.o

4. 將lckj4412_led_app.c拷貝到Linux任意目錄下並交叉編譯測試程式
$ arm-none-linux-gnueabi-gcc lckj4412_led_app.c –o lckj4412_led_app
sudo cp lckj4412_led_app /tftpboot/rootfs

5. 編譯LED驅動到核心中
 配置核心時按“空格”選擇,配置完成後儲存退出
$ make menuconfig
Device Drivers —>
Character devices —>
<*>LCKJ4412 LED Device Support
 儲存退出,重新編譯後把uImage拷貝到tftpboot下
$ make uImage
$ cp arch/arm/boot/uImage /tftpboot
重新啟動開發板,載入核心並執行。在終端下執行下面操作
 建立裝置節點
# mknod /dev/ledc5000
 執行測試程式並觀察現象
# ./fs4412_led_app
6. 編譯LED驅動為模組
 配置核心時按“空格”選擇,配置完成後儲存退出
$ make menuconfig
Device Drivers —>
Character devices —>
< M>FS4412 LED Device Support
 儲存退出,重新編譯後把zImage拷貝到tftpboot下,把驅動模組拷貝到/tftpboot/rootfs下
$ make uImage modules
$ cp arch/arm/boot/uImage /tftpboot/
$ cp drivers/char/lckj4412_led_drv.ko /tftpboot/rootfs/
重新啟動開發板,linux執行起來後在終端下操作
 建立裝置節點
# mknod /dev/ledc5000
 載入LED驅動模組
#insmod lckj4412_led_drv.ko
 執行測試程式並觀察現象
# ./lckj4412_led_app

4.9 SD卡驅動移植

1. 修改裝置樹檔案
$ vim arch/arm/boot/dts/exynos4412-lckj4412.dts
修改
這裡寫圖片描述
為:

[email protected]12530000 {
        bus-width = <4>;
        pinctrl-0 = <&sd2_clk &sd2_cmd &sd2_bus4>;
        cd-gpios = <&gpx0 7 0>;
        cd-inverted =  <0>;
        pinctrl-names = "default";
        /*vmmc-supply = <&mmc_reg>;*/
        status = "okay";
    };

2. 配置核心
$ make menuconfig
Device Drivers —>
<*> MMC/SD/SDIO card support —>
<*> Secure Digital Host Controller Interface support
<*> SDHCI support on Samsung S3C SoC
File systems —>
DOS/FAT/NT Filesystems —>
<*> MSDOS fs support
<*> VFAT (Windows-95) fs support
(437) Default codepage for FAT
(iso8859-1) Default iocharset for FAT
-*- Native language support —>
<*> Codepage 437 (United States, Canada)
<*> Simplified Chinese charset (CP936, GB2312)
<*> ASCII (United States)
<*> NLS ISO 8859-1 (Latin 1; Western European Languages)
<*> NLS UTF-8
3. 編譯核心和裝置樹
$ make uImage
$ make dtbs
4. 測試:
拷貝核心和裝置樹檔案到/tftpboot目錄下
$ cp arm/arm/boot/uImage /tftpboot
$ cp arch/arm/boot/dts/exynos4412-lckj4412.dtb /tftpboot/
啟動開發板會有如下內容顯示:
[ 1.620000] mmc0: new high speed SDHC card at address cd6d
[ 1.625000] mmcblk1: mmc0:cd6d SE08G 7.28 GiB
[ 1.630000] mmcblk1: p1(mmcblk1 為裝置名 p1 為分割槽名)
掛載,注意不要掛在EMMC的分割槽
#mount -t vfat /dev/mmcblk1p1 /mnt
檢視/mnt/目錄即可看到sd卡中內容

4.10 USB驅動的移植

1. 修改裝置樹檔案
$ vim arch/arm/boot/dts/exynos4412-lckj4412.dts
新增如下內容:

    usbphy: [email protected]125B0000 {
    #address-cells = <1>;
    #size-cells = <1>;
        compatible = "samsung,exynos4x12-usb2phy";
        reg = <0x125B0000 0x100>;
        ranges;

        clocks = <&clock 2>, <&clock 305>;
        clock-names = "xusbxti", "otg";

        usbphy-sys {
            reg = <0x10020704 0x8 0x1001021c 0x4>;
        };
    };

    [email protected]12580000 {
        status = "okay";
        usbphy = <&usbphy>;
    };
[email protected]08 {
        compatible = "smsc,usb3503";
        reg = <0x08 0x4>;
        connect-gpios = <&gpm3 3 1>;
        intn-gpios = <&gpx2 3 1>;
        reset-gpios = <&gpm2 4 1>;
        initial-mode = <1>;
    };

2. 配置核心
make menuconfig
Device Drivers —>
[*] USB support —>
<*> EHCI HCD (USB 2.0) support
<*>EHCI support for Samsung S5P/EXYNOS SoC Series
<*> USB Mass Storage support
<*> USB3503 HSIC to USB20 Driver
USB Physical Layer drivers —>
<*> Samsung USB 2.0 PHY controller Driver
SCSI device support —>
<*> SCSI device support
<*> SCSI disk support
<*> SCSI generic support
3. 編譯核心和裝置樹
$ make uImage
$ make dtbs
4. 測試:
拷貝核心和裝置樹檔案到/tftpboot目錄下
$ cp arm/arm/boot/uImage /tftpboot
$ cp arch/arm/boot/dts/exynos4412-lckj4412.dtb /tftpboot/
啟動目標板並在目標板上完成如下操作:
插入U盤顯示如下
[ 72.695000] usb 1-3.2: USB disconnect, device number 3
[ 74.435000] usb 1-3.2: new high-speed USB device number 4 using exynos-ehci
[ 74.555000] usb-storage 1-3.2:1.0: USB Mass Storage device detected
[ 74.560000] scsi1 : usb-storage 1-3.2:1.0
[ 75.645000] scsi 1:0:0:0: Direct-Access Kingston DataTraveler 160 PMAP PQ: 0 ANSI: 4
[ 75.660000] sd 1:0:0:0: Attached scsi generic sg0 type 0
[ 76.695000] sd 1:0:0:0: [sda] 15556608 512-byte logical blocks: (7.96 GB/7.41 GiB)
[ 76.700000] sd 1:0:0:0: [sda] Write Protect is off
[ 76.705000] sd 1:0:0:0: [sda] No Caching mode page found
[ 76.710000] sd 1:0:0:0: [sda] Assuming drive cache: write through
[ 76.725000] sd 1:0:0:0: [sda] No Caching mode page found
[ 76.730000] sd 1:0:0:0: [sda] Assuming drive cache: write through
[ 76.760000] sda: sda1 (sda是裝置名 sda1是分割槽名)
[ 76.770000] sd 1:0:0:0: [sda] No Caching mode page found
[ 76.770000] sd 1:0:0:0: [sda] Assuming drive cache: write through
[ 76.780000] sd 1:0:0:0: [sda] Attached SCSI removable disk
在終端上執行掛載的裝置與上邊顯示相關
# mount -t vfat /dev/sda1 /mnt
# ls
可以檢視到U盤內容,即完成實驗。

4.11記憶體除錯

1、 解壓memwatch.-2.7.1.tar.gz,在解開的目錄下新增程式碼memtest.c

#include <stdlib.h>
#include <stdio.h>
#include "memwatch.h"
int main(int argc,char **argv)
{
char *ptr1;
char *ptr2;
        ptr1 = malloc(512);
        ptr2 = malloc(512);
ptr1[512]= 'A';
        ptr2 = ptr1;
free(ptr2);
free(ptr1);
return 0;
}

2、 修改Makefile
Makefile檔案為:

memtest:
        $(CC) -DMEMWATCH -DMW_STDIO memtest.cmemwatch.c

5、執行make並在主機上執行a.out,執行後會生成一個記錄檔案memwatch.log,內容如下:

============= MEMWATCH 2.71 Copyright (C) 1992-1999 Johan Lindh =========

Started at Thu Jan  1 00:08:33 1970

Modes: __STDC__ 32-bit mwDWORD==(unsigned long)
mwROUNDALLOC==4 sizeof(mwData)==32 mwDataSize==32

overflow: <3> memtest.c(12), 512 bytes alloc'd at <1> memtest.c(8)
double-free: <4> memtest.c(13), 0x1a1b4 was freed from memtest.c(12)

Stopped at Thu Jan  1 00:08:33 1970

unfreed: <2> test.c(9), 512 bytes at 0x1a3e4    {FE FE FE FE FE FE FE FE FE FE FE FE FE FE FE FE ................}

Memory usage statistics (global):
N)umber of allocations made: 2
L)argest memory usage      : 1024
T)otal of all alloc() calls: 1024
U)nfreed bytes totals      : 512
//overflow: <3> memtest.c(12)緩衝區溢位,當程式執行到第12行free(ptr2)才檢測到的;

512 bytes alloc'd at <1>memtest.c(8)
//表示出錯緩衝區的大小為512位元組,是在memtest.c的第8行分配的。很容易發現程式碼的ptr1[512]='A'出現錯誤。
double-free: <4> memtest.c(13), 0x1a7f4 was freed from memtest.c(12)
//double-free: <4> memtest.c(13)是一個雙重釋放的錯誤,表示程式執行到13行的時候才檢測到。
0x1a7f4 was freed from memtest.c(12)
//表示首地址為 0x1a7f4的記憶體在12行已經被釋放。
Stopped at Wed Dec 31 19:00:38 1969
unfreed: <2> test.c(9), 512 bytes at 0x1a3e4        {FE FE FE FE FE FE FE FE
//表示一塊記憶體沒有釋放,表示這塊記憶體是在memtest.c的第9行分配,大小為512位元組,首地址為0x1a3e4。
Memory usage statistics (global):
N)umber of allocations made: 2
L)argest memory usage      : 1024
 //程式結束時能夠是使用的最大動態記憶體
 T)otal of all alloc() calls: 1024 //總共分配的動態記憶體
U)nfreed bytes totals      : 512
 //表示未釋放的記憶體

4.12核心除錯

1、通過OOPS資訊中PC暫存器的值可以知道出錯指令的地址,通過棧回朔資訊可以知道出錯時的函式呼叫的關係,根據這兩點可以很快定位錯誤。
2、修改drivers/net/ethernet/davicom/dm9000.c,在dm9000_probe函式中u32 id_val;下增加下面語句:

int *ptr  =NULL;
*ptr=0xff;

3、編譯核心下載到開發板上,核心啟動會出現如類似下資訊:

Unable to handle kernel NULL pointer dereference at virtual address 00000000
pgd = c0004000
[00000000] *pgd=00000000
Internal error: Oops: 805 [#1] PREEMPT SMP ARM
Modules linked in:
CPU: 1 PID: 1 Comm: swapper/0 Not tainted 3.14.0 #11
task: ee8a0000 ti: ee8a4000 task.ti: ee8a4000
PC is at dm9000_probe+0x1c/0x8f0
LR is at platform_drv_probe+0x18/0x48
pc : [<c0277cc8>]    lr : [<c0247f7c>]    psr: 60000153
sp : ee8a5e48  ip : 00000000  fp : 00000000
r10: c052a4fc  r9 : 00000000  r8 : c0591e98
r7 : 00000000  r6 : ee97c810  r5 : ee97c800  r4 : 00000000
r3 : 000000ff  r2 : 00000000  r1 : ee8a5de8  r0 : ee97c800
Flags: nZCv  IRQs on  FIQs off  Mode SVC_32  ISA ARM  Segment kernel
Control: 10c5387d  Table: 4000404a  DAC: 00000015
Process swapper/0 (pid: 1, stack limit = 0xee8a4240)
Stack: (0xee8a5e48 to 0xee8a6000)
5e40:               ee975cf0 00000000 ee1503a8 00000001 c0561afc ee150438
5e60: 00000000 ee97c810 c0591e98 ee97c810 00000000 c0591e98 c0561afc c052a4fc
5e80: 00000000 c0247f7c c0247f64 c05d931c c0591e98 c0246668 ee97c810 c0591e98
5ea0: ee97c844 00000000 c054332c c0246804 c0591e98 c0246778 00000000 c0244fbc
5ec0: ee805478 ee9771c0 c0591e98 eeb73a00 c0590028 c0245e28 c04c3128 c0591e98
5ee0: 00000000 c0591e98 00000000 c054e2ac c059f280 c0246e1c 00000000 ee8a4000
5f00: 00000000 c00087b4 ee903b00 c05c3d50 60000153 c0571c00 60000100 c0571c00
5f20: 00000000 00000000 c0571bfc 00000000 c0505bc8 ef7fc918 00000089 c0034c6c
5f40: c04ca680 c0505338 00000006 00000006 00000000 c054e2c8 c054e2cc 00000006
5f60: c054e2ac c059f280 00000089 c052a4fc 00000000 c052ac4c 00000006 00000006
5f80: c052a4fc c003e0dc 00000000 c03b46ec 00000000 00000000 00000000 00000000
5fa0: 00000000 c03b46f4 00000000 c000e4b8 00000000 00000000 00000000 00000000
5fc0: 0000000000000000 00000000 00000000 00000000 00000000 00000000 00000000
5fe0: 00000000 00000000 00000000 00000000 00000013 00000000 ffffffff ffffffff
[<c0277cc8>] (dm9000_probe) from [<c0247f7c>] (platform_drv_probe+0x18/0x48)
[<c0247f7c>] (platform_drv_probe) from [<c0246668>] (driver_probe_device+0x100/0x210)
[<c0246668>] (driver_probe_device) from [<c0246804>] (__driver_attach+0x8c/0x90)
[<c0246804>] (__driver_attach) from [<c0244fbc>] (bus_for_each_dev+0x58/0x88) 
[<c0244fbc>] (bus_for_each_dev) from [<c0245e28>] (bus_add_driver+0xd8/0x1cc) 
[<c0245e28>] (bus_add_driver) from [<c0246e1c>] (driver_register+0x78/0xf4)
[<c0246e1c>] (driver_register) from [<c00087b4>] (do_one_initcall+0x30/0x144)
[<c00087b4>] (do_one_initcall) from [<c052ac4c>] (kernel_init_freeable+0xfc/0x1c8)
[<c052ac4c>] (kernel_init_freeable) from [<c03b46f4>] (kernel_init+0x8/0xe4)

錯誤分析:
1、第一行
Unable to handle kernel NULL pointer dereference at virtual address 00000000
說明是空指標造成的錯誤
2、暫存器資訊主要是PC的值
PC is at dm9000_probe+0x1c/0x8f0
和pc : [ ]

錯誤定位:$ arm-none-linux-gnueabi-objdump -D vmlinux > vmlinux.dis
檔案vmlinux.dis非常大開啟需要一定時間
這裡寫圖片描述
對於大多數情況,從反彙編程式碼定位到C程式碼並不會如此容易,需要有較強的閱讀彙編程式碼的能力。另外一種方法是通過addr2line去定位
$ arm-none-linux-gnueabi-addr2line 0xc0277cc8-e vmlinux -f
重要:該實驗完成後不要忘記恢復被修改的程式碼

小貼士:核心除錯方法簡單分析

1、addr2line: 解決oops錯誤
a – oops訊息
oops(也稱 panic),稱程式執行崩潰,程式崩潰後會產生oops訊息。應用程式或核心執行緒的崩潰都會產生oops訊息,通常發生oops時,系統不會發生宕機,而在終端或日誌中列印oops資訊。
當使用NULL指標或不正確的指標值時,通常會引發一個 oops 訊息,這是因為當引用一個非法指標時,頁面對映機制無法將虛擬地址映像到實體地址,處理器就會向作業系統發出一個”頁面失效”的訊號。核心無法”換頁”到並不存在的地址上,系統就會產生一個”oops”。
oops 顯示發生錯誤時處理器的狀態,包括 CPU 暫存器的內容、頁描述符表的位置,以及其一些難理解的資訊。這些訊息由失效處理函式(arch/*/kernel/traps.c)中的printk 語句產生。較為重要的資訊就是指令指標(EIP),即出錯指令的地址。
b – 解決方法
arm-linux-addr2line 地址 -e vmlinux -f
將Pc地址轉換成對應的行號

2、通過printk列印資訊
核心通過 printk() 輸出的資訊具有日誌級別,日誌級別是通過在 printk() 輸出的字串前加一個帶尖括號的整數來控制的,如 printk(“<6>Hello, world!/n”);。核心中共提供了八種不同的日誌級別,在 linux/kernel.h 中有相應的巨集對應。

#define KERN_EMERG    "<0>"    /* system is unusable */
#define KERN_ALERT    "<1>"    /* action must be taken immediately */
#define KERN_CRIT     "<2>"    /* critical conditions */
#define KERN_ERR      "<3>"    /* error conditions */
#define KERN_WARNING  "<4>"    /* warning conditions */
#define KERN_NOTICE   "<5>"    /* normal but significant */
#define KERN_INFO     "<6>"    /* informational */
#define KERN_DEBUG    "<7>"    /* debug-level messages */

所以 printk() 可以這樣用:

 printk(KERN_INFO "Hello, world!/n");。

未指定日誌級別的 printk() 採用的預設級別是 DEFAULT_MESSAGE_LOGLEVEL,這個巨集在 kernel/printk.c 中被定義為整數 4,即對應KERN_WARNING。
在 /proc/sys/kernel/printk 會顯示4個數值(可由 echo 修改), 分別表示當前控制檯日誌級別、未明確指定日誌級別的預設訊息日誌級別、最小(最高)允許設定的控制檯日誌級別、引導時預設的日誌級別。
當 printk() 中的訊息日誌級別小於當前控制檯日誌級別時,printk 的資訊(要有/n符)就會在控制檯上顯示。但無論當前控制檯日誌級別是何值,通過 /proc/kmsg (或使用dmesg)總能檢視。另外如果配置好並運行了 syslogd 或 klogd,沒有在控制檯上顯示的 printk 的資訊也會追加到 /var/log/messages.log 中。

char myname[] = "chinacodec/n";
printk(KERN_INFO "Hello, world %s!/n", myname);