Портирование Android

Материал из AndroidFan

Перейти к: навигация, поиск

Итак, вы имеете устройство, на котором хотите использовать Android/Linux и нашли, что оно не поддерживается системой. И у вас есть желание запустить его во что бы то ни стало - тогда осталось только портировать его на ваш аппарат. Вот приблизительная карта действий, которой я советую придерживаться при этом:

Содержание

Шаг 1

Во-первых, узнайте - нет ли уже существующего проекта портирования android/linux на ваше целевое устройство. И если есть - свяжитесь с ними и скоординируйте свою работу с командой.

Шаг 2

Если вы не нашли существующего проекта - отправьте письмо admin­lists@handhelds.org с просьбой создать для вас сайт и рассылку. (наиболее предпочтительный метод, чем создание веток в форумах, вместо создания страницы/вики). Если ваше устройство HTC - то тогда здесь - [1]

Шаг 3

Залог вашего успеха не сколько навыки программирования, сколько постоянная активность по проекту, сообщения в форумах, списках рассылки, каналах IRC, jabber-конференциях и пр, чтобы обратить на проект внимание обладателей устройств, имеющих необходимые навыки и желание. Важно сознавать, что на этом этапе самая главная роль - роль лидера, для сбора сплоченной команды разработчиков, поскольку одному человеку порт как правило не под силу.

Шаг 4

Узнайте, разбирал ли кто ваше устройство и есть ли его фотографии (PCB) Возможно искать здесь - [2]

Шаг 5

Попытайтесь найти спецификации, даташиты или reference-manual-ы для вашего устройства.

Шаг 6

Найдите на устройстве код FCC и найдете хорошие фото и описания на сайте [3]

Шаг 7

Разберите устройство и сфотографируйте (желательно каждый значимый чип отдельно). Составьте таблицу чипов и по маркировке найдите полное название и ссылку на описание устройство

Шаг 8

Для каждого значимого устройства попытайтесь достать спецификации и желательно Technical Reference Manual or User Guide. все найденные даташиты обязательно рассортируйте по чипам и сохраните в надежном месте - они имеют обыкновение пропадать и потом их очень трудно найти. Учтите что некоторые производители весьма активно борются против утечек информации, будьте осторожны. У других же производителей можно наоборот узнать все до мелких подробностей.

Поиск даташитов начните с поиска в гугле, по-разному варьируя поисковые запросы - "bcm4070 datasheet", "broadcomm 4070 datasheet", "bcm4070 file:*.pdf", "bcm4070 manual" и прочее Если поиск ничего не дал - используйте сайты по типу:

Пробуйте писать на форумах и просите выслать вам на почту. Часто достаточно написать просьбу на соотв. китайском или корейском форуме на китайском или корейском языке и вам вышлют даташит по электронке (как правило translate.google.ru бывает более чем достаточно) Заведите знакомства с нужными людьми и пр...

Если во всех предыдущих случаях у вас не получилось что-то найти, есть методика поиска даташитов, если известен производитель. Это использование crawler-ов. Например Web-Harvest [4]. Вот пример статьи на русском [5]

Шаг 9

Попытайтесь найти проекты уже реализовавшие код для ваших чипов (драйвера) или аналогичных.

Шаг 10

Убедитесь что у вас есть компьютер с операционной системой Linux (можно и в эмуляторе) и необходимые инструменты:

  • JTAG - адаптер
  • последовательный кабель или USB (зависит от устройства)
  • IDA Pro с набором скриптов под ваш процессор и, если повезет декомпилятор Hex Rays
  • IDA SDK - поскольку вам может понадобиться активное написание скриптов или плагинов
  • Эмулятор и отладчик - скорее всего qemu и gdb, но зависит от платформы.
  • CollabREate - сервер и плагин для IDA Pro - удобен для командного реверс-инжиниринга, в чем то подобен DVCS (Git, Mercurial, etc...)
  • HaREt - если у вас Windows-телефон
  • Processor Instruction Guide - для каждого процессора, установленного в системе (Application Processor, Baseband Processor, DPSs, other microcontollers)

Шаг 11

Выберите подходящее ядро - соотвествующее вашей архитектуре и требованиям, или максимально к этому приближенное (или несколько - и слепите их в одно). Много всяких лежит тут http://git.kernel.org/

Шаг 12

Выберите подходящий загрузчик, для использования на этапе прошивки. Наиболее подходящие - U-Boot, Redboot, Yaboot (ppc), openiboot (iPhone, iPad), HaREt(windows mobile)

Шаг 13

Теперь у вас есть два пути - или делать реверс-инжиниринг дампа прошивки для поиска информации о драйверах или пытаться все делать методом проб и ошибок. Мы конечно же выберем второе :) (к сожалению для устройств не на WM это сложнее чем здесь описано)

Шаг 14

Подготовим пакет утилит разработки под наше устройство (toolchain) - его мы можем использовать готовый, либо собрать самостоятельно (например используя OE, buildroot, crosstool-ng, etc...)

Шаг 15

Соберем ядро с наиболее подходящими параметрами (при этом для максимальной совместимости надо выбрать самый минимум поддерживаемых возможностей и отключить модули)

Шаг 16

Запустим его (или через HaREt или напрямую) и будем обрабатывать всю информацию о внутренних интерфейсах и их совместной работе (порядок соединений GPIO, USB-OTG, SPI, I2C, прерывания и регистры, бут-ромы и DSP-BIOS-ы, супервизоры и взаимодействие с PC при подключении USB и JTAG)

Шаг 17

На основе полученной информации составим карту загрузки и вызовов устройств, таблицы регистров, прерываний, GPIO, I2C, OTG соединений. Сделаем дампы прошивок чипов, их RAM и регистров. Сделаем анализ используемых в каждом чипе архитектур.

Шаг 18

Добавление поддержки вашего оборудования в загрузчик: Что содержиться в каталогах загрузчика, что придеться редактировать для поддержки новой платформы, на примере U-Boot:

  1. include
    • заголовочный файл с конфигурацией CPU
      • специфичные определения для доступа к функциям CPU
      • определения регистров процессора.
      • файл настроек машины в Configs/ dir.
        • Hardware Config
        • Карта памяти
        • Конфигурация периферии
  2. board
    • код драйвера и инициализации flash памяти (off-chip)
    • конфигурация внешней SDRAM Configurations
    • конфигурация внешних устройств (FPGA, PCI etc)
  3. cpu
    • конфигурация для каждого чипа
    • драйвера для внутренних Serial / USB
    • драйвера для внутренней Flash памяти
    • <core>/cpu.c Содержит операции чтения/записи регистров, перезегрузки процессора, работы с I/D кешем, установки стеков для IRQ и FIQ, и пр...
    • <core>/interrupt.c - этот файл содержит функции работы с прерываниями и таймером.
    • <core>/start.S - этот файл содержит код старта для ARM926EJS например.
  4. lib-xxx (например lib-ppc, lib-arm,..)
    • содержит файл board.c, который обьявляет специфичную для этой платформы функциональность
    • <arch>linux.c, например armlinux.c - файл содержит реализацию команды "bootm", используемую для расположения образа ядра в памяти.Эта команда отправляет параметры ядру запускает ядро, передавая ему управление.
    • div0.c - файл содержит обработчик исключения "деление на 0"

Порядок портирования U-Boot:

  1. Создать файл устройства в каталоге сonfigs.
  2. Создайте новую цель в главном Makefile для включения этого файла в include.
  3. Сконфигурируйте u-boot редактированием файла устройста в in include/configs/dir
    • конфигурация "сhip select" для обьявляемой карты памяти в том числе и для Flash, SDRAM и другой периферии отображаемой в памяти.
    • базовые адреса памяти
    • параметры памяти Flash (размер сектора, количество секторов, и др)
    • адреса регистров
    • конфигурация SDRAM
    • Аргументы, передаваемые ядру Linux.
    • И многое другое...
  4. Задайте карту памяти для линковщика
    • CFG_MONITOR_BASE – адрес u-boot в SDRAM
    • CFG_GBL_DATA_SIZE – глобальный размер данных
    • CFG_MALLOC_LEN – размер кучи
    • CFG_ENV_ADDR, CFG_ENV_SIZE – пространство для хранения конфигурации u-boot.
    • CFG_BOOTMAPSZ – пространство операционной системы.
  5. Определение всех фунций драйверов, которые необходимы для работы вашей архитектуры размещением файлов с соответсвующими именами устройств в новый каталог с именем машины (устройства) в каталоге board/.
  6. Измените файлы CPU и lib-cpu если необходимо.
  7. make <new board Config target>
  8. make all
  9. На выходе получаем u-boot.bin (запускаемый файл)

Порой иногда бывает проще написать свой собственный загрузчик, но это не рекомендуемый путь.

Шаг 19

Добавление поддержки оборудования в ядро Linux

Каталоги ядра Linux, с которыми придется работать:

  1. Documentation/ - документация ядра, постоянно будет помогать разрешать вам спорные вопросы;
  2. arch/ - архитектурно-специфичный код, содержит подкаталоги ppc, arm etc. Каждая из этих поддиректорий содержит несколько интересных каталогов
  3. arch/arm/kernel/ архитектурно-специфичный код ядра.
    • arch/arm/kernel/head.S Содержит точку входа ядра. Файл также содержит обработку исключений ядра. Файл инициализирует стек, очищает BSS, загружает MMU, и вызывает start_kernel() (в init/main.c), которая по сути первая C функция выполняемая ядром.
    • arch/arm/kernel/setup.c setup_arch() функция вызывает специфичные для архитектуры функции инициализации устройств. Тут же производиться обработка командной строки ядра и настройка памяти. Адреса начала и конца образа ramdisk также находятся тут.
  4. arch/arm/mm Архитектурно специфичный код управления памятью
  5. arch/arm/lib Архитектурно специфичные библиотечные функции, vsprintf и др.
  6. arch/arm/mach-mx3/ Платформо-специфичный код - здесь больше всего работы для портирования.
    • arch/arm/mach-mx3/setup.c (also named with board name) Платформо-специфичная инициализации. Конфигурация чипов, прерываний производиться здесь.
    • arch/arm/mach-mx3/irq.c Код специфичной для платформы обработки прерываний.
  7. drivers/ Драйвера устройств.
  8. include/asm-arm заголовочные файлы специфичные для архитектуры
  9. init/ Код инициализации ядра.

Порядок портирования ядра:

Создайте каталог для вашей платформы (пример my_arm11_board) в каталоге arch/arm/. Этот каталог должен содержать обработку прерываний, таймер, инициализацию и настройку (setup) Если ваша платформа имеет не стандартный процессор, то вам надо в первую очередь определить таблицу TLB3 (translation lookaside buffers). Добавьте опцию (CONFIG_MYCPU) для включения кода, специфичного для вашего процессора. Каталоги $(TOPDIR)/arch/MY_ARCH/kernel и $(TOPDIR)/arch/MY_ARCH/mm содержат код, который необходимо попровить для вашего нового процессора.

Файл $(TOPDIR)/arch/MY_ARCH/kernel/head.S содержит kernel_entry, точку входа ядра. Также файл содержит код обработки исключений ядра. Вот что происходит в нем на псевдокоде:

  1. Set desired 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 coprocessors
  10. GOTO start_kernel
  11. GOTO 10


Следующий шаг - определение типа процессора. $(TOPDIR)/include/asm/bootinfo.h содержит определения типа процессора (MYCPU) и группы машины (MY_MACH_GROUP). Это значение необходимо для MMU для вашего процессора CPU, а также для инорфмации в /proc.

LEAF(cpu_probe) la t3, my_cputype 

li t2, MYCPU /* include/asm-mips/bootinfo.h */ b probe_done sw t2, (t3) END(cpu_probe)

Дальше производиться установка стека. Затем очищается bss. Затем выполняется функция prom_init(). Затем сбрасываются кэши и TLB и устанавливаются функции работы с кешем в loadmmu(). Затем отключаются сопроцессоры отличные от 0 и вызывается start_kernel(). $(TOPDIR)/arch/MY_ARCH/mm содержит обработчики TLB и кэша.

Функция prom_init(), в файле $(TOPDIR)/arch/MY_ARCH/MY_PLATFORM/prom.c, добавляет необходимые аргументы для ядра, ожидаемые от загрузчика.

  1. int __init prom_init (int argc, char **argv, char **envp) 
  2. { 
  3. unsigned int mem_limit; // set upper limit to maximum physical RAM (32MB) mem_limit = 32 * 1024 * 1024; 
  4.  
  5. // the bootloader usually passes us argc/argv[] . //In the present case, these arguments are not //passed from the bootloader. The kernel wants // one big string. put it in arcs_cmdline, which ater gets copied to command_line //(see arch/mips/kernel/setup.c) 
  6.  
  7. strcpy (arcs_cmdline, *root=/dev/ram*); 
  8.  
  9. mips_machgroup = MY_MACH_GROUP; 
  10.  
  11. // set the upper bound of usable memory mips_memory_upper = KSEG0 + mem_limit; 
  12.  
  13. printk(*Detected&nbsp;%dMB of memory\n*, mem_limit &gt;&gt; 20); 
  14.  
  15. return 0; 
  16. }

Функция start_kernel() в файле $(TOPDIR)/init/main.c содержит вызовы функций инициализации, которые вы должны изменить для поддержки вашего оборудования

  1. asmlinkage void __init start_kernel(void) { char * command_line; 
  2.  
  3. /* * Interrupts are still disabled. Do necessary setups, then * enable them */ 
  4. lock_kernel(); 
  5. printk(linux_banner); 
  6. setup_arch(&command_line, &memory_start, &memory_end); 
  7. memory_start = paging_init(memory_start, memory_end); 
  8. trap_init(); 
  9. init_IRQ(); 
  10. sched_init(); 
  11. time_init(); 
  12. parse_options(command_line); ...

Функция setup_arch() в файле $(TOPDIR)/arch/MY_ARCH/kernel/setup.c. Специфические для платформы функции инициализации вызываются тут. Командная строка ядра и начало и конец памяти обновляются здесь. Начало и конец образа ramdisk также обьявляются здесь.

  1. __initfunc(void setup_arch(char **cmdline_p, unsigned long * memory_start_p, unsigned long * memory_end_p)) { #ifdef CONFIG_BLK_DEV_INITRD 
  2.  
  3. #if CONFIG_BLK_DEV_INITRD_OFILE
  4.  
  5. extern void *__rd_start, *__rd_end; 
  6.  
  7. #endif 
  8. #endif
  9.  
  10. myplatform_setup(); strncpy(command_line, arcs_cmdline, CL_SIZE); 
  11. *cmdline_p = command_line; 
  12.  
  13. *memory_start_p = (unsigned_long) &_end; 
  14. *memory_end_p = mips_memory_upper; 
  15.  
  16. #ifdef CONFIG_BLK_DEV_INITRD 
  17. #if CONFIG_BLK_DEV_INITRD_OFILE
  18.  
  19. // Use the linked-in ramdisk // image located at __rd_start. 
  20. initrd_start = (unsigned long)&__rd_start; 
  21. initrd_end = (unsigned long)&__rd_end; 
  22. initrd_below_start_ok = 1; 
  23. if (initrd_end > memory_end) { 
  24. printk(*initrd extends beyond end of memory * *(0x%08lx &gt; 0x%08lx)\ndisabling initrd\n*, initrd_end, memory_end); 
  25. initrd_start = 0; 
  26. } 
  27.  
  28. #endif 
  29. #endif
  30.  
  31. }

Платформо-специфичная инициализация находиться в файле $(TOPDIR)/arch/MY_ARCH/MY_PLATFORM/setup.c. Различные базовые адреса и платформо-специфичные адреса устанавливаются здесь.

  1. __initfunc(void myplatform_setup(void)) { 
  2.  
  3. 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; 
  4.  
  5. /* * 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; 
  6.  
  7. /* * 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; 
  8.  
  9. #endif
  10.  
  11. #ifdef CONFIG_BLK_DEV_IDE
  12.  
  13. ide_ops = &amp;std_ide_ops; 
  14.  
  15. #endif
  16.  
  17. #ifdef CONFIG_VT 
  18. #if defined(CONFIG_DUMMY_CONSOLE)
  19.  
  20. conswitchp = &amp;dummy_con; 
  21.  
  22. #endif 
  23. #endif
  24.  
  25. /* 
  26.  
  27.  * just set rtc_ops &amp;&amp; pci_ops; forget the rest
  28.  */
  29.  
  30. rtc_ops = &amp;myplatform_rtc_ops; pci_ops = &amp;myplatform_pci_ops; // or other main bus of your platform }

Функция trap_init() копирует top-level обработчики прерываний в KSEG0 вектор, зависящий от типа процессора. Обработчики прерываний находятся в файлах $(TOPDIR)/arch/MY_ARCH/MY_PLATFORM/irq.c и int-handler.S. Большинство систем содержат встроенный железный контроллер прерываний. Этот контроллер отлавливает одно из внешних прерываний процессора. Код может быть изменен таким образом чтобы контроллер прерываний обрабатывал прерывания ядра.

Установка прерывания производиться функцией set_except_vector(). Дальше инициализируется контроллер прерываний. Если включен режим удаленной отладки, то функция set_debug_traps() должна быть корректно настроена для использования отладчиком.

  1. static void __init myplatform_irq_setup (void) { 
  2. set_except_vector (0, myplatform_handle_int); 
  3. // Initialize InterruptController 
  4. InterruptController_Init(IsrTable); 
  5. #ifdef CONFIG_REMOTE_DEBUG
  6. printk (*Setting debug traps - please connect the remote debugger.\n*); 
  7. set_debug_traps (); 
  8. breakpoint (); 
  9. #endif
  10. }

Top-level обработчик прерываний сначала сохраняет все регистры и затем отключает все прерывания. Регистр CAUSE проверяется для ждения причины прерывания. Если это прерывание таймера, то вызывается соотвествующее ISR. Если это не прерывание таймера, проводиться проверка не прерывание ли это для контроллера прерываний.

  1. NESTED(myplatform_handle_int, PT_SIZE, ra) .set .noat SAVE_ALL CLI .set .at 
  2.  
  3. mfc0 .s0, CP0_CAUSE .# get irq mask 
  4.  
  5. /* 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 
  6.  
  7. /* Wheee, a timer interrupt. */ move .a0, sp jal .timer_interrupt nop . .# delay slot 
  8.  
  9. j ret_from_irq nop . .# delay slot 
  10.  
  11. 1: beq .a0, zero, 1f nop 
  12.  
  13. /* Wheee, combined hardware level zero interrupt. */ jal .InterruptController_InterruptHandler move .a0, sp .# delay slot 
  14.  
  15. j .ret_from_irq nop . .# delay slot 
  16.  
  17. 1: /* 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)[/code] Обработчик прерывания для контроллера прерываний получает адрес вектора прерываний, который вызвал прерываниe и затем вызывает обработчик, соответствующий причине вызова прерывания [code]void InterruptController_InterruptHandler ( struct pt_regs *regs ) 
  18.  
  19. { IntVector intvector; struct irqaction *action; int irq, cpu = smp_processor_id(); 
  20.  
  21. InterruptControllerGetPendingIntVector(&amp;intvector); InterruptControllerGetPendingIntSrc((&amp;irq); 
  22.  
  23.   action = (struct irqaction *)intvector;
  24.  
  25. if ( action == NULL ) { printk(*No handler for hw0 irq:&nbsp;%i\n*, irq); return; } 
  26.  
  27. hardirq_enter(cpu); 
  28.  
  29. &handler(irq, &dev_id, regs); 
  30. kstat.irqs[0]irq++; hardirq_exit(cpu); 
  31.  
  32. } // InterruptController_InterruptHandler ()

Функции request_irq(), free_irq(), enable_irq() и disable_irq() должны быть добавлены для вашей платформы. request_irq() используется для установки прерывания для устройства вызвавшего прерывание. free_irq() нужен для освобождения памяти, выделенной для прерывания. enable_irq() нужен для вызова функции котроллера прерываний, которая включает необходимую линию прерывания. disable_irq() нужна для отключения выбранной линии прерывания.

Файл $(TOPDIR)/arch/MY_ARCH/MY_PLATFORM/time.c содержит платформоспецифичный код таймера. Регистр счетчика и регистр сравнения вместе необходимы для таймера. Когда таймер активен, регистр счетчика содержит запущенный счетчик, увеличивающийся на единицу при каждом тике процессора. Событие таймера и прерывание генерируется если значения регистров счетчика и сравнения становятся равны, регистр счетчика сбрасывается. После сброса регистра, он снова начинает увеличиваться при каждом тике. Обработчик прерывания таймера (ISR) нужен для вызова do_timer(). Запись в регистр сравнения запускает таймер.

Для консоли и отладки необходим работающий драйвер последовательного устройства. Минимальный набор необходимых функций, для драйвера последовательного устройства: a) serial_console_init()-для регистрации консоли ядра для работы printk() перед тем как драйвер консоли будет нормально инициализирован. B) serial_console_setup()- для инициализации последовательного порта c) serial_console_write(struct console *console, const char *string, int count)- для записи нескольких символов

Подключите кабель к последовательному устройству,затем наладьте сообщение с устройством на компьютере (можно использовать minicom например)

Драйвер последовательного интерфейса может быть использован для создания устройства терминала tty. Много драйверов последовательных устройств доступно в каталоге $(TOPDIR)/drivers/char. Выберите наиболее близкий к используемому оборудованию драйвер и модифицируйте его. CONFIG_SERIAL (serial support) должна быть установлена в Y в "make config."

  1. Используйте файлы с платформы, найболее близкой вашей.
  2. Создайте каталог для вашей платформы (my_arm11_board) в include/asm-arm/ Этот каталог должен содержать заголовочные файлы для вашей платформы # Редактируйте файлы в этих каталогах до просветления :)
  3. Добавьте вашу платформу в систему сборки ядра редактированием Config и Makefile-в в каталоге arch/arm/
  4. Make defconfig (для сборки дефолтной конфигурации вашей платформы)
  5. Make

Полезные советы при портировании ядра:

Активно используйте вызовы printk() внутри и вокруг кода, который вы хотите отладить. Это работает. Всегда.

Использование GDB также может быть очень полезно. Как работает удаленный GDB - сервер gdb запущен на машине для разработки и разговаривает с ядром, запущенным на целевой системе через последовательный интерфейс. Для этого включите CONFIG_REMOTE_DEBUG = Y в ядре. putDebugChar(char ch) и getDebugChar() - две фунции, которые должны быть изменены для корректной работы через последовательный интерфейс для удаленной отладки через gdb.

Для начала, заставьте работать минимум необходимых функций для драйвера tty через последовательный порт как в $(TOPDIR)/include/linux/tty_ldisc.h

Не надо использовать фонарик для отладки!!! (это неудобно :) ) Просто многие не знают что в самом начале, когда не работает функция printk() отлично работает early_printk() Используйте человеческую отладку!

Если у вас что-то не работает и вы не можете понять почему - используйте IDA Pro, чтобы посмотреть как решили эту проблему в оригинальной OS.

Шаг 20

Отладка - доведение работы ядра и загрузчика до стадии работы всех устройств (никаких ошибок и ворнингов в выводе загрузчика и ядра)

Шаг 21

Портирование минимального набора системных утилит, библиотек и их оберток. Принцип одинаков для Linux и для Android.

Сайты по портированию

Android на Asus P535 (англ.)
Портирование Linux (англ.)
Gentoo Linux на Asus P525 и P535 (рус.)
Android на Sharp Zaurus (англ.)
Много информации о портировании от Google (англ.)