해당 웹페이지 글 내용으로만 봐서는 저것만 있으면 모든게 술술 풀릴듯 한데 아쉽게도 현재는 구할 수가 없다. 어쩔수 없이 삽질을 시작 했다.
기본적으로 SD부팅을 하는데 SD카드를 제거하면 USB 부트모드로 진입가능하고 OS에서 신규 디바이스로 인식한다. USB 부트모드를 활용하면 될듯 하여 자료를 뒤져 봤으나, USB 부팅에 대한 자세한 내용은 역시 찾을 수가 없다.
VTC라고하는 드라이버와 FWDN이라고 하는 SW를 가지고 펌웨어 업데이트등 조작이 되는듯 한데, 역시나 FWDN SW에 대한 USB프로토콜등 자세한 관련내용이나 자료도 없다.
CPU벤더가 중국회사인줄 알았는데 Telechips라고 하는 한국에 있는 회사였다. 회사 홈페이지에 가봐도 의무적으로 공개해야하는 포팅된 리눅스 커널이외 쓸만한 자료는 없었다.
개발자의 접근을 용이하게 해주면 더 잘 팔릴텐데...
여튼, 도움이 될만한 자료가 거의 전무하다.
보드에 TP가 몇개 보이길레 몇 군데 찔러보고 UART 핀을 찾아내고, 힌트가 될 만한 VTC/FWDN을 붙잡고 시작해봤다.
구해진 VTC드라이버는 구버전이라 TCC8935를 지원하지 않는다. VTC드라이버 패키지에서 '*.ini' 파일을 찾아 TCC8935의 USB VIP/PID(0x140E/0xB098)를 쑤셔넣고 강제로 드라이버를 인식 시켰다.
FWDN SW를 구동하면서 USBpcap(http://desowin.org/usbpcap)으로 USB패킷을 모조리 수집한 후 USB bulk-in/out 패킷을 분석하였다. 패킷 분석을 통해 내용을 대충 알아내고, libusb를 사용해 CPU에 코드를 쑤셔넣어 가면서 테스트 후 자작한 부트로더를 올릴 수 있었다.
먼저 USB 패킷 크기는 512바이트이고, 2종류로 구성된다. 하나는 헤더 패킷, 두번째는 칩에 다운로드할 펌웨어 바이너리 패킷으로 구성된다. 헤더 패킷 포멧은 아래와 같다.
There are some TPs at the board, and after digging, I found UART pin from the board. When I plugged the micro USB cable for power on, I could receive the bootloader and linux/Android kernel boot messages. Yes, the board is OK, but it's booted again and again. Probably the file system has been crashed.
It's hard to find useful information about the development or firmwares on the net. I've googled for a few days but no luck without outdated VTC driver and FWDN for Windows.
That VTC driver I've got was not support the TCC8935. Moreover there is no detailed user manual for the FWDN. After modification of the '*.ini' file, I could install the device driver with outdated-VTC driver compulsorily in Windows.
I decided to analyze the USB packet with USBpcap(http://desowin.org/usbpcap).
After digging with the USB packets for the FWDN and compulsory installed VTC driver through USBpcap and test code injection, I've found out working USB protocol format. First of all, USB bulk-in/out size is 512byte.
The USB transaction is seperated into two parts, one is header and the other is main body for binary data transfer. USB header packet is like below.
+--------------------------+
| 32bit header key-code | -> 0xf0000000, 0xf0000001
+--------------------------+
| 32bit download size | -> download binary size in byte
+--------------------------+
| 32bit start address | -> jump address after download
+--------------------------+
| 32bit download address | -> download address
+--------------------------+
헤더 전송 이후 바이너리 데이터를 USB로 전송하면 된다. 코드로 구현하면 아래와 같다.
After sending the USB header, you can send the binary code. This sequence is same as older TCC7xx or TCC8xx series but the header is little different.
---------------------------------------------------------
unsigned int head[512>>2]= { 0, };
...
p_dev= dev_open(); // device open
...
// send header
head[0]= key_code;
head[1]= bin_size;
head[2]= start_addr;
head[3]= load_addr;
usb_bulk_write( p_dev, 0x01, (char*)&head[0], 512, 500 );
usb_bulk_read( p_dev, 0x82, (char*)p_rx, 512, 500 );
...
...
// send binary
usb_bulk_write( p_dev, 0x01, (char*)p_bin, bin_size, 5000 );
usb_bulk_read( p_dev, 0x82, (char*)p_rx, 512, 500 );
...
---------------------------------------------------------
key_code : 0xf0000000 or 0xf0000001
bin_size : binary size in byte
start_addr: jump address after download
load_addr : download address
p_dev : USB device handle
512 : USB bulk-in/out size
500 : timeout in ms
USB bulk-out이후 반드시 bulk-in을 읽어줘야 정상적으로 동작 되었다.
헤더의 key-code가 FWDN 패킷 분석시 0xf0000000, 0xf0000001, 0xf0000006이 식별되었다. key-code가 0xf0000000일 경우 start address로 명시된 주소지를 콜한다. 무조건 분기가 아니고 함수 호출 형태이다. key-code가 0xf0000001일 경우 start address를 콜하지 않는다. 그냥 메모리로 다운로드만 한다. 패킷 분석시 0xf0000006도 있었는데 이건 뭔지 모르겠다 (메모리 읽기?). 여기서 중요한게 0xf0000000와 0xf0000001를 잘 사용하는 것이다.
CPU 부팅시 DDR 메모리는 초기화가 되어 있지 않으므로 DDR메모리로 커널이나 부트로더등을 다운로드 할 수가 없다. 이때 0xf0000000을 이용해 내부 램에 DDR을 초기화 하는 코드만 전송하고, 두번째는 0xf0000001을 이용해 리눅스 커널을, 세번째는 다시 0xf0000000을 적용하고 부트로더를 전송후 부트로더로 점프하면 된다.
USB 부트모드에서 CPU로 다운로드하기 위한 프로그램(usb_loader.exe)을 만들었다. 이 글 마지막에 드라이버와 프로그램 다운로드 링크를 걸어 둔다.
자작한 usb_loader.exe 프로그램은 '-h', '-l', '-s', '-f'의 옵션 사용이 가능하다.
There are several header key-code, 0xf0000000, 0xf0000001, 0xf0000006. Key-code 0xf0000000 is for download firmware and call the address that has been specified by start address in the header. I figured out that the CPU calls like a C-function call, it's not an unconditional JUMP with 0xf0000000 header key-code. Key-code 0xf0000001 is for just download, nothing happened after download with 0xf0000001 header key-code. There was 0xf0000006 key-code at the captured USB packet but I couldn't figured out what is this for. (memory read?)
Calling usb_bulk_read() was mandatory after usb_bulk_write() in my test and always 4byte is returned with usb_bulk_read().
I've made my own USB downloader(usb_loader.exe) for the TCC8935 from my USB packet digging result. It's uses libusb.
You can download my USB downloader at the end of this text.
The '-h', '-l', '-s', '-f' options are available for this program.
-h: 0xf0000000 or 0xf0000001
-l: download address
-s: jump address after download
-f: binary file
자작한 프로그램으로 아래와 같이 사용 할 경우 리눅스와 부트로더가 DDR에 적재되고 부트로더로 점프할 수 있다.
You can boot the TCC8935 CPU with bootloader/linux like below sequence. 4byte returned data is always 0x00000000 with 1st and 2nd download, but 0xffffffff after 3rd download.
// DDR initialization code injection
c:\>usb_loader.exe -h0xf0000000 -l0x10008000 -s0x10008000 -fddr_init.bin
// wait 100~500ms
// linux kernel injection
c:\>usb_loader.exe -h0xf0000001 -l0x80008000 -s0x80008000 -fImage.bin
// wait 100~500ms
// download bootloader and jump to bootloader
c:\>usb_loader.exe -h0xf0000000 -l0xbff00000 -s0xbff00000 -fbootloader.bin
ddr_init.bin에는 DDR초기화 코드만 들어 있다. DDR초기화 이후 그냥 리던하면 된다. ddr_init.bin에서도 CPU 모든 레지스터에 접근이 가능하기 때문에 테스트 코드 같은거 집어 넣고 돌려봐도 된다. 리눅스 커널 다운로드등 다음 스텝으로 넘어 가려면 그냥 DDR 초기화 함수에서 리턴하면 된다. 이후 리눅스 커널을 0xf0000001로 다운로드만 하고, 다시 0xf0000000로 부트로더를 다운로드하면 부트로더 동작이 가능하다.
메모리에 커널과 부트로더가 있으니, 부트로더에서 ATAG만들고 커널로 점프하면 된다.
Telechips 홈페이지에서 구한 linux-3.4.35 커널을 빌드하고 테스트 해봤는데 일단 start_kernel()까지 진입은 가능했다. 부팅은 되는데 'Calibration delay loop...'에서 멈춘다. 추적해 보니 jiffies가 변경되기를 기다리는 루틴이 있는데 거기서 멈춘다. 타이머 인터럽트 서비스가 되지 않는다. 다른 삽질이 기다리고 있는듯 하다.
The ddr_init.bin is for DDR initialization, if you finish the DDR-init. function just return, then you can proceed to next step. The next step is just injection the kernel with 0xf0000001 header key-code into the DDR. Now you can jump to the bootloader after 3rd download, then you can call linux kernel with ATAG.
I've tested with linux-3.4.35 from the Telechips homepage. I could reached start_kernel() at my initial test but hang on 'Calibration delay loop...' message. It seems to be not serviced timer interrupt since the kernel stucked at waiting code for jiffies count.
[ 0.000000] Booting Linux on physical CPU 0
[ 0.000000] Initializing cgroup subsys cpu
[ 0.000000] Linux version 3.4.35 (root@kali) (gcc version 4.9.3 (crosstool-NG crosstool-ng-1.22.0) ) #45 SMP PREEMPT Tue Dec 12 17:00:18 KST 2017
[ 0.000000] CPU: ARMv7 Processor [412fc09c] revision 12 (ARMv7), cr=10c53c7d
[ 0.000000] CPU: PIPT / VIPT nonaliasing data cache, VIPT aliasing instruction cache
[ 0.000000] Machine: m805_893x
[ 0.000000] Memory policy: ECC disabled, Data cache writealloc
[ 0.000000] L310 cache controller enabled
[ 0.000000] l2x0: 16 ways, CACHE_ID 0x410000c6, AUX_CTRL 0x70150001, Cache size: 524288 B
[ 0.000000] pll_0: cpu clock source
[ 0.000000] pll_1: 500000 kHz (Fixed)
[ 0.000000] pll_2: 0 kHz (Fixed)
[ 0.000000] pll_3: audio clock source
[ 0.000000] pll_4: 0 kHz (Fixed)
[ 0.000000] pll_5: 0 kHz (Fixed)
[ 0.000000] div_pll_0: cpu clock source
[ 0.000000] div_pll_1: 0 kHz (Fixed)
[ 0.000000] div_pll_2: 0 kHz (Fixed)
[ 0.000000] div_pll_3: audio clock source
[ 0.000000] div_pll_4: 0 kHz (Fixed)
[ 0.000000] div_pll_5: 0 kHz (Fixed)
[ 0.000000] xin: 24000 kHz (Fixed)
[ 0.000000] clocktable initialized
[ 0.000000] TCC clock driver initialized
[ 0.000000] PERCPU: Embedded 8 pages/cpu @c120b000 s11200 r8192 d13376 u32768
[ 0.000000] Built 1 zonelists in Zone order, mobility grouping on. Total pages: 260096
[ 0.000000] Kernel command line: vmalloc=480M console=ttyTCC0,115200n8 initrd=0xB0400000,0x00061B28 ramdisk=8192 root=/dev/ram0
[ 0.000000] PID hash table entries: 4096 (order: 2, 16384 bytes)
[ 0.000000] Dentry cache hash table entries: 131072 (order: 7, 524288 bytes)
[ 0.000000] Inode-cache hash table entries: 65536 (order: 6, 262144 bytes)
[ 0.000000] Memory: 1024MB = 1024MB total
[ 0.000000] Memory: 1028852k/1028852k available, 19724k reserved, 507512K highmem
[ 0.000000] Virtual kernel memory layout:
[ 0.000000] vector : 0xffff0000 - 0xffff1000 ( 4 kB)
[ 0.000000] fixmap : 0xfff00000 - 0xfffe0000 ( 896 kB)
[ 0.000000] vmalloc : 0xe1800000 - 0xff000000 ( 472 MB)
[ 0.000000] lowmem : 0xc0000000 - 0xe1000000 ( 528 MB)
[ 0.000000] pkmap : 0xbfe00000 - 0xc0000000 ( 2 MB)
[ 0.000000] modules : 0xbf000000 - 0xbfe00000 ( 14 MB)
[ 0.000000] .text : 0xc0008000 - 0xc066b28c (6541 kB)
[ 0.000000] .init : 0xc066c000 - 0xc069cbc0 ( 195 kB)
[ 0.000000] .data : 0xc069e000 - 0xc0878d78 (1900 kB)
[ 0.000000] .bss : 0xc0878d9c - 0xc0a01730 (1571 kB)
[ 0.000000] SLUB: Genslabs=11, HWalign=64, Order=0-3, MinObjects=0, CPUs=2, Nodes=1
[ 0.000000] Preemptible hierarchical RCU implementation.
[ 0.000000] NR_IRQS:195
[ 0.000000] sched_clock: 32 bits at 12MHz, resolution 83ns, wraps every 357913ms
[ 0.000000] Initialize the clocksource device.... rate[24000000]
[ 0.000000] Console: colour dummy device 80x30
[ 0.000000] [UART00] setting ulcon: 00000003, umcon: 00000002, brddiv to 0, baud 115200, uart_clk 0
[ 0.000000] console [ttyTCC0] enabled
[ 0.278620] Calibrating delay loop...