จากประสบการณ์ในการทำงานด้านการออกแบบระบบสมองกลฝังตัวของแอดมินที่ผ่านมา เราอาจจะเจอกับงานที่มีการเปลี่ยนแปลงไปจากบอร์ดอ้างอิง (Reference design) เนื่องจากเหตุผลเรื่องราคา ความต้องการของลูกค้า อายุของผลิตภัณฑ์ในตลาด แนวโน้มของเทคโนโลยี่ หรือข้อจำกัดเรื่องกฎหมายสิ่งแวดล้อม โดยเฉพาะอุปกรณ์พวกหน่วยความจำต่างๆ เช่น nand flash หรือ SDRAM ซึ่งอาจจะทำให้เฟิร์มแวร์ ของบอร์ดอ้างอิงทำงานไม่ได้บนบอร์ดต้นแบบ ทำให้เราต้องมีการแก้ไขโค้ดต้นฉบับให้สามารถทำงานกับบอร์ดที่ออกแบบมาใหม่ได้
การแก้ไขนี้จะมากน้อยขนาดไหน อยู่ที่โชคของแต่ละคนนะครับ ในบางงานเราอาจจะได้เป็นนักพัฒนากลุ่มแรกในโลกที่ได้ทำงานกับอุปกรณ์นั้น ข้อดีคือ มันท้าทายมาก ข้อเสียคือมันใหม่จนเราไม่สามารถปรึกษาใครได้เลย แม้กระทั่งกับ FAE (Field Application Engineer)
ในบทความนี้ จึงอยากนำตัวอย่างของการทำ u-boot porting ยูบูตเวอร์ชั่นเก่าที่รองรับบอร์ด mini2440 อยู่แล้ว มาเป็นเวอร์ชั่น 2016.07 มาให้ดูเป็นแนวทางสำหรับคนที่สนใจ แต่ต้องทำความเข้าใจก่อนว่าในตัวอย่างนี้เป็นการทำเพื่อศึกษาเท่านั้น เพราะโดยปกติถ้าหากเรามีบูตโหลดเดอร์ที่ทำงานได้อยู่แล้ว และไม่มีความจำเป็นต้องใช้ฟีเจอร์ใหม่ๆของโค้ดเวอร์ชั่นที่ใหม่กว่า ก็ไม่มีความจำเป็นที่จะต้องพอร์ตโค้ดไปเป็นเวอร์ชั่นใหม่เลย
เลือกบอร์ดเป้าหมาย
ในตัวอย่างนี้แอดมินเลือกที่ใช้ โปรเจค QEMU mini2440 เหตุผลหลักเนื่องจากความชอบส่วนตัว เหตุผลรองลงมาคือความสะดวกในการดีบัก เพราะเป็นแพลตฟอร์มแบบเปิดทั้ง hardware และ software มีตัวอย่างของโค้ดยูบูตให้ศึกษา ทำให้การพอร์ตไม่ยากจนเกินไปจึงเหมาะสำหรับศึกษาในระดับเบื้องต้น
เหตุผลอีกเรื่องหนึ่งคือโค้ดสายหลักของโปรเจคยูบูตไม่ได้รองรับบอร์ดนี้ (u-boot main line) ซึ่งถือว่าเป็นข้อดีสำหรับเราเพราะว่าจะทำให้เราเห็นความเปลี่ยนแปลงของโค้ดส่วนต่างๆของยูบูต ตั้งแต่เริ่มจนกระทั่งสามารถรองรับบอร์ด mini2440 ได้อย่างสมบูรณ์
รายละเอียดสเปคของ mini2440 สามารถอ่านได้จาก http://www.friendlyarm.net/products/mini2440
เปรียบเทียบ u-boot 1.3.2 vs u-boot 2016.07
โค้ดยูบูตที่รองรับบอร์ด mini2440 เวอร์ชั่นล่าสุดที่สามารถหาได้จากอินเตอร์เน็ต คือเวอร์ชั่น u-boot 1.3.2 และตัวโปรเจคได้หยุดการอัพเดทตั้งแต่ปี 2012 แล้ว เพราะฉะนั้นการพอร์ตมาเป็นเวอร์ชั่น 2016.07 ก็ดูจะน่าสนใจอยู่บ้าง
เราเริ่มจากการสำรวจโครงสร้างไดเรคทอรี่ ของยูบูต 1.3.2 เทียบกับ เวอร์ชั่น 2016.07 จะพบว่ามีความแตกต่างกันอยู่หลายส่วน เนื่องจากในยูบูตเวอร์ชั่นตั้งแต่ 2014.10 ได้เปลี่ยนมาใช้ kbuild และ kconfig ในการกำหนดขั้นตอนในการสร้างส่วนประกอบต่างๆแทนที่จะรวมไว้ใน Makefile อย่างเดียว เนื่องจากตัวโปรเจคเริ่มมีขนาดใหญ่ขึ้นรวมถึงต้องการให้ยูบูตซอสโค้ดมีความสามารถในการปรับแต่งโมดูลต่างได้แบบเดียวกับ ลินุกซ์เคอร์เนล (make menuconfig)
จากการเปรียบเทียบไดเรคทอรี่ชั้นแรกจะพบว่า ไดเรคทอรี่ส่วนที่เป็นขึ้นอยู่กับสถาปัตยกรรม (architecture specific) ได้ถูกย้ายและรวบรวมไว้ภายใต้ arch ทั้งหมด ซึ่งช่วยดูให้เข้าใจง่ายขึ้นมาก
เหตุผลที่ยกเรื่องโครงสร้างไดเรคทอรี่มาให้ดูด้านบน เพราะอยากให้เข้าใจว่าส่วนใดของโปรเจคที่เราต้องเข้าไปแก้ไข เนื่องจากเราต้องการทำให้โค้ดของโปรเจคนี้รองรับบอร์ด mini2440 ดังนั้นส่วนที่เป็น board specific ก็จะเป็นส่วนหลักที่เราจะเข้าไปทำงานในครั้งนี้ แต่ก็ไม่ได้หมายความว่าเราจะไม่ต้องไปแก้ส่วนอื่นเลย เพราะยังไงก็ยังมีส่วนที่เกี่ยวข้องกันอยู่ ยกตัวอย่างเช่น หากมีความต้องการเพิ่มไดร์เวอร์โค้ดของ NAND flash ที่ใช้อยู่บน mini2440 เราก็จำเป็นต้องเข้าไปแก้ในส่วน generic code ด้วย
เลือกวิธีกันก่อน
การพอร์ตโค้ดเพื่อให้สามารถทำงา
แอดมินจะขอแบ่งขั้นตอนการพอร์ตโค้ดออกเป็นสองช่วงนะครับ ช่วงแรกคือการแก้ไขระบบการคอมไพล์ยูบูตให้รองรับบอร์ดใหม่ (complie time) และช่วงหลังคือการแก้ไขโค้ดให้ทำงานได้ (runtime)
การแก้ไขระบบการสร้างให้รองรับบอร์ดใหม่ (Adding new board to build system)
ไฟล์ README.kconfig แนะนำให้ตั้งค่าของตัวแปรคอนฟิก CONFIG_SYS_CPU , CONFIG_SYS_SOC และ CONFIG_SYS_VENDOR ใน Kconfig เพื่อรองรับบอร์ดใหม่ ซึ่งเท่าที่ไล่ดูจากไฟล์คอนฟิกข
ตัวแปร CONFIG_TARGET_{BOARDNAME} ของ smdk2410 นั้นถูกกำหนดค่ามาจากไฟล์ configs/smdk2410_defconfig ซึ่งเราก็จะทำเหมือนกันสำหรับ mini2440 เพื่อคง “สไตล์” ของเดิมไว้ รวมถึงป้องกันปัญหาจากการแก้
สร้าง default config
เราจะเพิ่มไฟล์ดีฟอล์ทคอนฟิก สำหรับบอร์ด mini2440 เข้าไปก่อน เพื่อที่จะทำให้สามารถใช้ คำสั่ง make mini2440_defconfig เหมือนกับบอร์ดอื่นๆที่ u-boot 2016.07 รองรับ โดยคัดลอกมาจากไฟล์ configs/smdk2410_defconfig มาเป็น configs/mini2440_defconfig แล้วเปลี่ยนแปลงค่าตัวแปรบางตัว ด้านล่างเปรียบเทียบไฟล์ทั้งสอง
เพิ่มส่วนรองรับบอร์ดใหม่แบบเฉพาะเจาะจง (Board specific support files)
โค้ดสำหรับรองรับบอร์ดใหม่ โดยทั่วๆไป ควรวางไว้ภายใต้ board/<vendor>/<board>/ โดยที่ <vendor> จะถูกกำหนดค่ามาจากตัวแปร
สำหรับ mini2440 ในที่นี้ เราจะกำหนดให้ vendor = “friendlyarm” และ board = “mini2440” สำหรับการประยุกต์ไปใช้กับบอร์
ไฟล์ที่อยู่ภายใต้ board/friendlyarm/mini2440 มีจำนวน 4 ไฟล์ดังนี้
board └── friendlyarm └── mini2440 ├── Kconfig ├── lowlevel_init.S ├── Makefile └── mini2440.c |
Kconfig และ Makefile สามารถ copy มาจาก board/samsung/smdk2410/ แล้วแก้ไขให้รองรับบอร์ด mini2440 ได้ Kconfig ของบอร์ดไฟล์นี้ ทำหน้าที่กำหนดค่าของ ตัวแปร CONFIG_SYS_BOARD, CONFIG_SYS_VENDOR, CONFIG_SYS_SOC และ CONFIG_SYS_CONFIG_NAME
lowlevel_init.S และ mini2440.c เราจะใช้ไฟล์จาก u-boot 1.3.2 แล้วแก้ไขบางส่วนให้ทำงานกับ u-boot 2016.07 ได้ รายละเอียดการแก้ไข จะอธิบายในส่วนหลังนะครับ
เพิ่มเฮดเดอร์ไฟล์ของบอร์ดเป้าหมาย
เฮดเดอร์ไฟล์สำหรับบอร์ดใหม่
เราจะคัดลอก include/configs/
เพิ่มบอร์ดใหม่ในเมนู (Add a new entry to the board select menu)
ส่วนกำหนด board select menu อยู่ใน arch/<arch>/Kconfig หรือ arch/<arch>/*/Kconfig ในตัวอย่างของเราคือ arch/arm/Kconfig ซึ่งเราต้องเพิ่มให้ไฟล์นี้รวม board/friendlyarm/mini2440/
ตอนนี้ระบบการสร้างของยูบูตก็
การแก้ไขโค้ดให้ทำงานได้
include/configs/mini2440.h
ไฟล์นี้แอดมินคัดลอกมาจาก include/configs/smdk2410.h และต้องแก้ไขบางจุด
บรรทัดที่ 25 เนื่องจาก mini2440 QEMU ใช้ entry point เป็นตำแหน่ง 0x33F80000 (SDRAM) แทนตำแหน่ง 0x00000000 (NAND flash) ทำให้เราจำเป็นต้องแก้ค่า CONFIG_SYS_TEXT_BASE เพื่อให้โปรแกรม QEMU โหลดโปรแกรมยูบูตที่ได้จากการคอมไพล์ของเรา สอดคล้องกับ entry point ของ QEMU
อธิบายเพิ่มเติม เนื่องจากบอร์ด mini2440 ของจริงนั้นสามารถบูตได้ทั้งจาก NOR flash หรือ NAND flash โดยเลือกจากสวิตซ์ S2 บนบอร์ด และใช้ตำแหน่ง 0x00000000 เป็นค่าตำแหน่งเริ่มต้น ทำให้โปรแกรมยูบูตเริ่มทำงานที่ ROM mode ก่อนแล้วจึงทำการย้ายตำแหน่งไปทำงานบน RAM mode ทีหลัง
แต่สำหรับตัวโปรแกรม QEMU เลือกที่จะใช้ตำแหน่ง 0x33F80000 เป็นค่าตำแหน่งเริ่มต้น และยังใช้วิธีโหลดยูบูต (ELF format) ไปยังเมมโมรี่ตามที่ระบุไว้ในเฮดเดอร์ของไฟล์ ELF เราจึงจำเป็นต้องแก้ค่า CONFIG_SYS_TEXT_BASE ให้เป็น 0x33F80000 เพื่อที่จะได้ตรงกับตำแหน่งเริ่มต้นของ QEMU เนื่องจากขั้นตอนลิ้งเกอร์ จะใช้ค่านี้ในการระบุตำแหน่งในไฟล์ ELF
board/friendlyarm/mini2440/mini2440.c
ไฟล์นี้แอดมินเอามาจากยูบูต 1.3.2 แล้วแก้ไขให้ทำงานได้บนยูบูต 2016.07 ด้านล่างแสดงความต่างของโค้ดก่อนและหลังแก้ไข
จะเห็นว่าส่วนที่ต้องแก้ไขนั้นมีหลายบรรทัดเหมือนกัน แต่ยังถือว่าไม่มากเท่าไรนัก
บรรทัด 32 แสดงการแก้ไข path ของเฮดเดอร์ไฟล์ให้ตรงกับโครงสร้างของยูบูต 2016.07
บรรทัด 33 เราต้องรวม asm/arch/s3c24x0.h เข้ามาเพิ่มด้วย สำหรับรีจิสเตอร์ที่ต้องอ้างถึง
บรรทัด 59 ขั้นตอนการทำงานของยูบูต 2016.07 แบ่ง board initialize เป็นสองช่วงตามที่เคยอธิบายไว้ในบทความเรื่องขั้นตอนการทำงานของยูบูต แต่ในยูบูต 1.3.2 จะมีแค่ช่วงเดียว เราจึงต้องแยกฟังก์ชั่นนี้ออกเป็นสองส่วนและเปลี่ยนชื่อฟังก์ชั่นส่วนแรกเป็น board_early_init_f()
บรรทัด 60-176 ส่วนใหญ่เกือบทั้งหมดจะเป็นการแก้ชื่อรีจิสเตอร์จากตัวพิมพ์ใหญ่ไปเป็นตัวพิมพ์เล็กตามรูปแบบที่ยูบูต 2016.07 ใช้อ้างถึง
บรรทัด 184 board initialize ส่วนที่สอง
บรรทัด 273-282 ยูบูต 2016.07 นั้นต้องการฟังก์ชั่น board_flash_get_legacy() อันนี้แอดมินไปลอกมาจาก smdk2410.c เกือบทั้งดุ้น แต่ที่จริงเราก็ยังไม่ได้ใช้งานอะไร
board/friendlyarm/mini2440/lowlevel_init.S
ไฟล์นี้แอดมินเอามาจากยูบูต 1.3.2 แล้วแก้ไขบรรทัด 134 แค่ค่า TEXT_BASE ให้เป็น CONFIG_SYS_TEXT_BASE ตามรูปแบบของยูบูต 2016.07
common/board_f.c
ไฟล์นี้เป็นโค้ดส่วน generic ส่วนที่ต้องแก้ไขแสดงด้านล่าง
บรรทัดที่ 360-368 การแก้ไขส่วนนี้ จริงๆ แล้วหากโค้ดนี้นำไปใช้กับบอร์ดจริงก็คงไม่จำเป็นอะไร แต่เนื่องจากเราใช้ mini2440 QEMU ในการทดสอบยูบูตที่ได้จากการคอมไพล์ แล้ว QEMU มันดันโหลดยูบูตของเราไปบนตำแหน่ง 0x33F80000 ตั้งแต่ตอนเริ่มต้น ทำให้เกิดปัญหาในขั้นตอนย้ายยูบูตไบนารี่ (relocation) เนื่องจากมันเป็นตำแหน่งเดียวกันเป๊ะ จึงเกิดการทำลายตัวเองขึ้น
ทางแก้ของแอดมินคือขยับตำแหน่งการย้ายของยูบูตลงมา 0x1000000 ไบต์ เพื่อไม่ให้เกิดการเขียนทับตัวเอง โค้ดส่วนนี้ยังสามารถทำงานได้บนบอร์ดจริงด้วย
สำหรับคนที่เคยใช้ mini2440 QEMU กับ ยูบูต 1.3.2 คงมีคำถามว่าแล้วยูบูต 1.3.2 แก้ปัญหานี้อย่างไร แอดมินต้องบอกว่าตัวบูตโค้ดของ 1.3.2 (cpu/arm920t/start.S) มันเทพกว่าตัว 2016.07 คือมันมีส่วนการเช็คว่าตัวโค้ดถูกสตาร์ทมาจากตำแหน่ง 0x00000000 หรือไม่ ซึ่งถ้าใช่ มันจะข้ามขั้นตอนการย้ายตำแหน่งไปเลย จึงไม่เกิดปัญหานี้
ผลการทดสอบ
ผลการรัน u-boot.bin ที่ได้จากการแก้ไขโค้ด u-boot 2016.07 แสดงอยู่ในตารางด้านล่าง โดยเบื้องต้นแค่ทำให้มันสามารถทำงานไปจนถึง คอมมานด์พร้อมนี่ก็เล่นเอาหืดขึ้นคอเหมือนกัน แต่ก็สนุกดี ลองใช้คำสั่ง bdinfo ให้แสดงค่าตำแหน่งของเมมโมรี่ สำหรับค่าต่างๆ ก็ดูปกติ แต่จากเอาพุตนี้จะยังเห็น NAND flash แสดงค่า 0 ไบต์ เนื่องจากเรายังไม่ได้แก้ไข NAND driver
./../arm-softmmu/qemu-system-arm -M mini2440 -serial stdio -mtdblock ./mini2440_nand128.bin -show-cursor -usb -usbdevice keyboard -usbdevice mouse -monitor telnet::5555,server,nowait mini2440_init: Boot mode: NAND S3C: CLK=240 HCLK=240 PCLK=240 UCLK=57 QEMU: ee24c08_init DM9000: INIT QEMU MAC : 52:54:00:12:34:56 QEMU mini2440_reset: loaded default u-boot from NAND QEMU mini2440_reset: loaded override u-boot (size 6c400) S3C: CLK=240 HCLK=120 PCLK=60 UCLK=57 S3C: CLK=240 HCLK=60 PCLK=30 UCLK=57 S3C: CLK=240 HCLK=60 PCLK=30 UCLK=48 S3C: CLK=405 HCLK=101 PCLK=50 UCLK=48 U-Boot 2016.07-g27bb79a (Dec 02 2016 - 20:49:25 +0700) CPUID: 32440001 FCLK: 202.500 MHz HCLK: 202.500 MHz PCLK: 101.250 MHz DRAM: 64 MiB WARNING: Caches not enabled Flash: 0 Bytes NAND: 0 MiB *** Warning - bad CRC, using default environment In: serial Out: serial Err: serial Net: Net Initialization Skipped No ethernet found. MINI2440 # printenv baudrate=115200 bootdelay=5 ipaddr=10.0.0.110 netmask=255.255.255.0 serverip=10.0.0.1 stderr=serial stdin=serial stdout=serial Environment size: 144/65532 bytes MINI2440 # bdinfo arch_number = 0x000007CF boot_params = 0x30000100 DRAM bank = 0x00000000 -> start = 0x30000000 -> size = 0x04000000 current eth = unknown ip_addr = 10.0.0.110 baudrate = 115200 bps TLB addr = 0x32FF0000 relocaddr = 0x32F81000 reloc off = 0xFF001000 irq_sp = 0x32B70EF0 sp start = 0x32B70EE0 MINI2440 # reset resetting ... |
สำหรับคนที่สนใจอยากลองศึกษาโค้ดตัวอย่างเพิ่มเติมสามารถ pull ได้จาก github ตาม URL ด้านล่างนี้นะครับ
https://github.com/embedded-maker/u-boot-2016.07_mini2440.git
แล้วเจอกันใหม่ในโปรเจคต่อไปครับ