前言
早在2017年,因为酷爱刷机,把玩了很久的三星手机(GT-S7568)刷成了黑砖,bootloader无法启动,很是头疼。那时候没有现在流行的高通方案方便(可以9008),一旦固件底层损坏,通常维修店只能通过ISP(emmc飞线)重写底层固件。那时的我刚上初二,对这些只有浅薄的理解。我发现展讯也有一个USB下载模式,但三星没有在软件(u-boot-SPL)中实现,于是我只能通过自制一根工程线来进入。之后我尝试了多个工具,直接读写emmc,结果没有一个工具支持。原因是当时展讯出了多个版本的sc8810, 市面工具只支持sc8810g,使用NAND,而三星使用了sc8810es,使用emmc。后来我又找来emmc版本的其他手机的rom,将pac包内文件换成s7568的,用ResearchDownload写进去,还是无法亮屏。
2019年,我在github上翻到了展讯和FirefoxOS合作时开源的u-boot仓库,从提交历史里找到了展讯为三星做s7568的bringup的部分源码。编译下载,依然没有反应。我找到s7568的电路图,它的uart TX和下载模式gpio共用一根线接到了USB的ID端上。好不容易接出uart,却没有任何输出。这种情况下,由于我当时满天满地找8810的资料,对其启动流程有了一定的了解,我决定硬着头皮,把三星的SPL放进IDA,对照u-boot源码看看。
最终发现,三星的SPL除了比展讯简洁以外,最大的区别是它加载三星S-Boot的地址和展讯bringup里的不同。三星选择这个地址一定有其原因,于是我把uboot-spl的地址改成三星的,u-boot正常启动,直接点亮屏幕。有了uboot环境,后面的路就好走多了,不仅解决了问题,还能用开源的uboot换掉三星的SPL和S-Boot底层,支持fastboot。
底层启动流程
展讯默认:
IROM?----(emmc boot0 to IRAM)----> SPL-----(emmc boot1 to SDRAM)---->u-boot----(emmc GPT logical to SDRAM)----> vmjaluna(VLX) + Modem ThreadX + Android Kernel
三星定制:
IROM?----(emmc boot0 to IRAM)----> SPL-----(emmc GPT logical to SDRAM)---->S-Boot----(emmc GPT logical to SDRAM)----> vmjaluna(VLX) + Modem ThreadX + Android Kernel
IROM:展讯SoC内的ROM,Cortex-A5从这里启动,负责从外部flash加载SPL,也负责USB下载模式
SPL:u-boot前的loader,负责解决SDRAM初始化等问题,自身运行在片上IRAM里
u-boot或S-Boot:主要bootloader,负责各种初始化,显示第一屏,从flash读取固件和内核,之前就是把这个刷没了。
虚拟机
在后来修复u-boot支持的过程中,因为遇到不少问题,我才发现这个平台很是有意思:
展讯8810片上自带一个单核Cortex-A5(AP)和一个Ceva-X1622(DSP),性价比超高,CP都省了。他们之前的功能机方案采用ThreadX RTOS,上面既运行了基带协议层,也运行了MMI(手机的用户界面),而基带底层运行在DSP上。8810的基带沿用了之前的ThreadX,几乎就是把SC8800G的拿过来,删掉了MMI部分。于是问题来了,既要运行modem,又要运行安卓,CPU只有一个核,他们又不可能把基带搬进Linux内核(那不是家底都被看光了)。出于成本考虑,展讯采用了虚拟机方案,找到了当时做虚拟化方案的公司Red Bend(前VirtualLogix,再前Jaluna),以便同时运行两个OS。这就有了启动流程中的vmjaluna虚拟机。(以上观点均为臆测,如有差错欢迎指正)
图源:https://www.docin.com/p-1326014066.html
此时又出现了一个问题,Cortex-A5作为A9的精简版,没有ARM Virtuallization Extensions,不支持硬件虚拟化,Red Bend 便采用了半虚拟化方案。从内核源码可以看到半虚拟化用的一个巨大patch:https://github.com/himeno-hamster/sprd-kernel-common/commit/6b455af8469f7b86e09f2a838ede389a4d810d90
VMJALUNA
互联网上能找到的信息基本上就只到这里了。我当时对ARM和虚拟化比较感兴趣,2020年时间较多,便想反汇编这个vmjaluna binary。我找到了一份vmjaluna的debug版,其中保留了一些uart logging。同时我也找到了SC6820的datasheet,(sc6820是sc8810砍掉了 移动3G支持 的版本,pdf可以通用)。
u-boot源码中可以看到,其从emmc上加载了几个镜像:
#define DSP_ADR 0x00020000 //DSP image
#define VMJALUNA_ADR 0x00400000 //VLX 虚拟机
#define FIXNV_ADR 0x00480000 //基带NV,固定数据,IMEI等
#define RUNTIMENV_ADR 0x004a0000 //基带NV,非固定数据
#define MODEM_ADR 0x00500000 //基带RTOS,guestOS 1
#define RAMDISK_ADR 0x05500000 //安卓ramdisk
#define KERNEL_ADR 0x04508000 //安卓内核,guestOS 2
u-boot加载完后设置Linux ATAGs,然后就直接通过vlx_entry()跳转进vmjaluna。
VLX进去之后首先来到图上的Virtuallizer,进行MMU配置,整个0-0x0FFFFFFF(前256M SDRAM)被映射到0xC0000000,然后映射uart等VLX本身要用的设备。
接着进行一些内存置零操作,准备nanokernel环境(虚拟机本身也是个微内核)
void *__fastcall vmjaluna_bconf(int a1)
{
char *v1; // r4
int v2; // r5
v1 = &byte_C043B000;
v2 = 0x2000;
do
{
*v1++ = 0;
--v2;
}
while ( v2 );
return sub_C043A090(a1);
}
在内置的description table里找到nanokernel入口并跳转:
void *__cdecl sub_C043A090(int a1)
{
int v1; // r5
int i; // r4
char **v3; // r2
void *result; // r0
v1 = 0;
dword_C043A734 = (int)&loc_C043A008;
bconf_init_sections(16);
for ( i = 0; ; i += 6 )
{
result = &banks_param;
if ( v1++ >= 6 )
break;
v3 = &(&vmjaluna_entries_desc)[i];
if ( *((_BYTE *)v3 + 16) == 0x11 )
((void (*)(void))v3[3])(); // find the type 0x11(nkernel_start)
}
return result;
}
终于来到main:
void __fastcall __noreturn nkernel_start(void *a1, int a2)
{
off_C042BE28 = &unk_C0438740;
dword_C042BE38 = 0xC04380A0;
main(dword_C04380A0, a1, a2);
}
void __fastcall __noreturn main(int *a1, void *a2, int atags)
{
//...
serial_init();
console_init(0);
printf("\n%s MH 4.1\n", "Red Bend VLX");
printf("%s\n\n", "Copyright (c) 2002-2011, Red Bend Software. All rights reserved.");
if ( *a1 )
{
print_nk();
printf("assertion failed at file main.c line #%d\n", 5804);
while ( 1 )
;
}
a1[1] = 1;
cpu_init();
dword_C0421628 = atags;
if ( *(_DWORD *)(atags + 4) != 0x54410001 ) // 0x54410001 ATAG_CORE
{
//...
}
main函数会初始化串口和NK控制台,CPU,中断控制器, 解析bootloader传来的ATAGs,初始化Timer(但是展讯没做),对各个guestOS进行映射(只有一个物理MMU),初始化nk的OS context,这个context将传给guestOS中的OS Plugin,提供一些半虚拟化的API。
{
nk_os_ctx->ready = (NkReady)nkcall_ready;
nk_os_ctx->hgetc = (NkHistGetc)nkcall_cons_hist_getchar;
nk_os_ctx->commit = nullsub_6;
nk_os_ctx->stop = (NkStop)nkcall_stop;
nk_os_ctx->wakeup = (NkWakeUp)nullsub_7;
nk_os_ctx->resume = (NkResume)nkcall_resume;
nk_os_ctx->xpost = (NkXIrqPost)sub_C0409940;
nk_os_ctx->restart = (NkRestart)nkcall_restart;
nk_os_ctx->upgrade = nullsub_6;
nk_os_ctx->binfo = (NkGetBinfo)nkcall_binfo;
nk_os_ctx->osctx_get = (NkOsCtxGet)nkcall_vcpu_get;
nk_os_ctx->wsync_all = *(NkWSyncAll *)(MEMORY[0xC] + 4);
nk_os_ctx->wsync_entry = *(NkWSyncEntry *)(MEMORY[0xC] + 8);
nk_os_ctx->flush_all = *(NkFlushAll *)(MEMORY[0xC] + 12);
nk_os_ctx->vfp_get = (NkVfpGet)nkcall_vfp_get;
nk_os_ctx->vfp_owned = 0;
nk_os_ctx->dev_alloc = (NkDevAlloc)nkcall_legacy_dev_alloc;
nk_os_ctx->pmem_alloc = (NkPmemAlloc)nkcall_legacy_pmem_alloc;
nk_os_ctx->smp_xirq_alloc = (NkSmpXIrqAlloc)nkcall_smp_xirq_alloc;
nk_os_ctx->smp_dev_add = (NkSmpDevAdd)nkcall_smp_dev_add;
nk_os_ctx->smp_pdev_alloc = (NkSmpPdevAlloc)nkcall_smp_pdev_alloc;
nk_os_ctx->smp_pmem_alloc = (NkSmpPmemAlloc)nkcall_smp_pmem_alloc;
nk_os_ctx->smp_pxirq_alloc = (NkSmpPxirqAlloc)nkcall_smp_pxirq_alloc;
nk_os_ctx->smp_time = (NkSmpTime)nkcall_smp_time;
nk_os_ctx->smp_time_hz = (NkSmpTimeHz)nkcall_smp_time_hz;
nk_os_ctx->os_vectors[6] = (NkVector)sub_C0408794;
nk_os_ctx->smp_cpu_start = (NkSmpCpuStart)nkcall_smp_cpu_start;
nk_os_ctx->smp_cpu_stop = (NkSmpCpuStop)nkcall_smp_cpu_stop;
nk_os_ctx->smp_yield = nkcall_smp_yield;
nk_os_ctx->smp_relax = (NkSmpRelax)nkcall_smp_relax;
nk_os_ctx->pad_ops[0] = (nku32_f)nkcall_smp_dxirq_alloc;
nk_os_ctx->smp_irq_connect = (NkSmpIrqConnect)nkcall_smp_irq_connect;
nk_os_ctx->smp_irq_disconnect = (NkSmpIrqDisconnect)nkcall_smp_irq_disconnect;
nk_os_ctx->smp_irq_mask = (NkSmpIrqMask)nkcall_smp_irq_mask;
nk_os_ctx->smp_irq_unmask = (NkSmpIrqUnmask)nkcall_smp_irq_unmask;
nk_os_ctx->smp_irq_eoi = (NkSmpIrqEoi)nkcall_smp_irq_eoi;
nk_os_ctx->smp_irq_affinity = (NkSmpIrqAffinity)nkcall_smp_irq_affinity;
nk_os_ctx->smp_irq_post = (NkSmpIrqPost)nkcall_smp_irq_post;
nk_os_ctx->smp_timer_alloc = (NkSmpTimerAlloc)nkcall_smp_timer_alloc;
nk_os_ctx->smp_timer_free = (NkSmpTimerFree)nkcall_smp_timer_free;
nk_os_ctx->smp_timer_info = (NkSmpTimerInfo)nkcall_smp_timer_info;
nk_os_ctx->smp_timer_start_periodic = (NkSmpTimerStartPeriodic)nkcall_smp_timer_start_periodic;
nk_os_ctx->smp_timer_start_oneshot = (NkSmpTimerStartOneShot)nkcall_smp_timer_start_oneshot;
nk_os_ctx->smp_timer_stop = (NkSmpTimerStop)nkcall_smp_timer_stop;
}
还要初始化各种虚拟设备,例如基带与安卓间的通信和网络共享,veth,vbpipe等。最后创建Boot CPU实例。各个虚拟设备(包括虚拟CPU)都有对应的descriptor进行描述,例如一个虚拟CPU里会描述各registers的值,操作系统设定的异常向量表地址等。
再接下来是两个函数,分别初始化CBSP,在BootCPU执行CBSP。CBSP全称Core BSP,是VLX中的Primary guest OS,类似于传统虚拟机如HyperV中的root partition,Xen中的Dom0一样,提供硬件驱动(IRQ,TImer,etc.),实现中断分发,内存映射,虚拟机启动等功能。展讯使用了Embedded CBSP,和VLX编译到同一个binary里。
prepare_vm((int)a1);
start_cbsp(a1);
signed int __fastcall nkcbsp_misc_init(NkOsCtx *a1)
{
sub_C0415C70(a1);
if ( !vpic_init() || !timer_init() || !vtick_init() )
return 0;
console_irq_init();
return 1;
}
int __fastcall prepare_vm(int result)
{
//......
while ( 1 )
{
//......
v7 = v6->vm;
v6->regs[13] = (nku32_f)&v6->stack[254];
v6->sp_svc = (nku32_f)&v6->stack[254];
result = 0xC0408B38;
v8 = v6->vcpuid;
v6->vcpu_flags = 1;
v6->pc_svc = 0xC0408B38;//设置SVC模式下的PC寄存器
v6->regs[0] = (nku32_f)v6;//R0寄存器保存了VCPU结构体,会传进CBSP
v6->regs[15] = (nku32_f)sub_C040C590;
v7[6] |= 1 << v8;
if ( v3[16] == 1 )
{
v6->vcpu_flags = 0;
result = 0xC0408BD8;
v3[13] = 0xC0408BD8;
}
}
}
}
void __fastcall __noreturn start_cbsp(_DWORD *a1)
{
//......
if ( v1 )
{
if ( MEMORY[0x2C] )
{
print_nk();
printf("assertion failed at file main.c line #%d\n", 3560);
while ( 1 )
;
}
if ( !sub_C0417D2C() )
{
print_nk();
printf("assertion failed at file main.c line #%d\n", 3561);
while ( 1 )
;
}
if ( !sub_C0417D44((int)v2) )
{
print_nk();
printf("panic at file main.c line #%d: ", 3564);
printf("Unable to initialize NK CBSP\n");
while ( 1 )
;
}
}
else
{
if ( MEMORY[0x2C] )
{
v3 = (void (__fastcall *)(int))MEMORY[0x34];
goto LABEL_20;
}
if ( !sub_C0417D2C() )
{
print_nk();
printf("panic at file main.c line #%d: ", 3569);
printf("No primary VM found\n");
while ( 1 )
;
}
print_nk();
printf("Embedded Core BSP used\n"); //展讯
if ( !nkcbsp_init(v2) )
{
print_nk();
printf("panic at file main.c line #%d: ", 3573);
printf("Unable to initialize NK CBSP\n");
while ( 1 )
;
}
}
v3 = (void (__fastcall *)(int))nkcbsp_entry_point;
LABEL_20:
v4 = v2->cpu;
if ( !v4 )
{
print_nk();
printf("assertion failed at file main.c line #%d\n", 2909);
while ( 1 )
;
}
//......
printf("(%d,%d) starting VCPU 0x%x (pc 0x%x arch 0x%x) [%d]\n", v2->id, v2->vcpuid, v2, v3, 0, LODWORD(v2->ttime));
v3((int)v2);
}
CBSP里是个大循环,不断在两个虚拟机之间切换,同时不断的开关中断,既保证正常虚拟机调度不被打断,也能及时分发处理guest IRQs。
void __fastcall __noreturn nkcbsp_entry_point(int a1)
{
j__nkcall_ready((_DWORD *)a1);
while ( 1 )
{
while ( sub_C040844C((NkOsCtx *)a1, *(_DWORD *)(a1 + 2648)) )
;
flip_irq();
}
}
int __fastcall sub_C040844C(NkOsCtx *a1, int a2)
{
nku32_f v2; // lr
NkOsCtx *v4; // r0
int v5; // r1
NkOsCtx *v6; // r10
a1->cur_prio = a2;
a1->regs[14] = v2;
v4 = (NkOsCtx *)sub_C0409488(a1);
v6 = v4;
if ( v4 == a1 )
return ((int (__fastcall *)(_DWORD))a1->regs[14])(0); //guest2 R14(LR)
sub_C0408334((int)v4, v5);
return ((int (__fastcall *)(_DWORD))v6->regs[14])(0); //guest3 R14(LR)
}
静态分析到这里,就很难进行下去了,估计因为vmjaluna的编译器优化开的很高,IDA出现了很多反编译错误:
if ( MEMORY[0x2C] )//显然不是访问0x2C,而应该是指针被弄掉了
{
v3 = (void (__fastcall *)(int))MEMORY[0x34];
QEMU
IDA是支持动态调试的,动态调试状态下往往能看到很多静态看不到的东西。动态调试有多种方式,比如ARM可以用jtag,sc8810的pdf写着有jtag(ARM or DSP),并且三星也给引出了测试点,但是飞起线来相当麻烦,况且调试起来VLX,modem,Linux要一起上,得有个好jtag仿真器速度才够。于是我选择了另一种方式,通过QEMU模拟SC8810。
要注意的是,QEMU官方并没有对展讯的模拟支持,需要自己实现对sc8810 SoC外设的模拟。我们要模拟运行VLX虚拟机,要模拟CPU,地址空间,中断控制器,定时器,uart。
QEMU官方并不支持模拟Cortex-A5,好在A5/A8/A9软件上差异不大,并且VLX默认带了A5,A8和高通scropion核心的支持,于是我暂时用A8模拟代替一下A5
QEMU自从引入qdev框架以后,采用了面向对象的编程模式,每个虚拟机都有一个“设备树”,由bus和device组成。每个device都会连在它的parent bus上,而一个device也可以提供bus给下级device连接(例如实现I2C,SPI控制器的时候)。
QEMU-machine
QEMU ARM模拟通常是板级模拟,例如模拟S7568这个主板/设备,首先要在hw/arm里建一个samsung-s7568.c,qemu初始化时会实例化这个machine,调用init方法,因此要把主板上各设备的实例化写在里面。
初始化过程除了要给machine添加SoC,内存,还要做好程序的加载。我实现了sc8810 bootrom的加载(但还不支持irom remap,不知道qemu怎么remap)和linux内核的加载(非VLX模式)
static void s7568_init(MachineState *machine)
{
SC8810State *sc8810;
Error *err = NULL;
int irom_size;
char *filename;
if (machine->ram_size != 768 * MiB) {
error_report("This machine can only be used with 768MiB RAM");
exit(1);
}
//其实CPU应该放在SoC类初始化里面的,但是还没有A5支持就暂时放这吧
/* Only allow Cortex-A8 for now before Cortex-A5 support is added */
if (strcmp(machine->cpu_type, ARM_CPU_TYPE_NAME("cortex-a8")) != 0) {
error_report("This board only supports cortex-a8 CPU");
exit(1);
}
sc8810 = SPRD_SC8810(object_new(TYPE_SPRD_SC8810));
object_property_add_child(OBJECT(machine), "soc", OBJECT(sc8810));
object_unref(OBJECT(sc8810));
if (!qdev_realize(DEVICE(sc8810), NULL, &err)) {
error_reportf_err(err, "Couldn't realize Spreadtrum SC8810: ");
exit(1);
}
/* Does not support IRAM/IROM remap at present */
memory_region_init_ram(&sc8810->sdram_0, NULL, "sdram 0",
256 * MiB, &error_abort);
memory_region_init_ram(&sc8810->sdram_1, NULL, "sdram 1",
256 * MiB, &error_abort);
memory_region_init_ram(&sc8810->sdram_2, NULL, "sdram 2",
256 * MiB, &error_abort);
memory_region_add_subregion(get_system_memory(), memmap[SDRAM_0].base,
&sc8810->sdram_0);
memory_region_add_subregion(get_system_memory(), memmap[SDRAM_1].base,
&sc8810->sdram_1);
memory_region_add_subregion(get_system_memory(), memmap[SDRAM_2].base,
&sc8810->sdram_2);
filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, machine->firmware);
if (filename) {
irom_size = load_image_targphys(filename, memmap[IROM_0].base,
memmap[IROM_0].size);
g_free(filename);
} else {
irom_size = -1;
}
if (machine->firmware) {
if (irom_size < 0 || irom_size > memmap[IROM_0].size) {
error_report("Could not load sc8810 irom '%s'", machine->firmware);
exit(1);
} else {
CPUState *cs = CPU(&sc8810->cpu);
cpu_reset(cs);
cpu_set_pc(cs, memmap[IROM_0].base);
}
}
else {
s7568_binfo.ram_size = machine->ram_size;
arm_load_kernel(&sc8810->cpu, machine, &s7568_binfo);
}
}
接下来就要实现SoC类了,我目前实现了sc8810的数字中断控制器(yes,还有个模拟的),3个通用(倒计时)定时器,一个系统计时器(正计时),串口暂时用PL011代替,ADI master没怎么写,但是能过VLX/modem/Linux里的数据校验,不会卡死/panic。
sprd-sc8810.c
static void sc8810_init(Object *obj) //创建8810 SoC里的各个Object
{
SC8810State *s = SPRD_SC8810(obj);
object_initialize_child(obj, "cpu", &s->cpu,
ARM_CPU_TYPE_NAME("cortex-a8"));
object_initialize_child(obj, "intc", &s->intc, TYPE_SPRD_SC8810_INTC);
object_initialize_child(obj, "gptimer", &s->gptimer, TYPE_SPRD_SC8810_GP_TIMER);
object_initialize_child(obj, "systimer", &s->systimer, TYPE_SPRD_SC8810_SYS_TIMER);
object_initialize_child(obj, "adi", &s->adi, TYPE_SPRD_SC8810_ADI);
pl011_create(memmap[UART_0].base, 0, serial_hd(0));
}
static void sc8810_realize(DeviceState *dev, Error **errp)
//实例化,qdev_realize和sysbus_realize会调用对应对象的实例化方法,作用是,如有些Timer设备需要
//Qemu的ptimer对象,创建ptimer操作在它的realize方法完成
{
SC8810State *s = SPRD_SC8810(dev);
SysBusDevice *sysbusdev;
MemoryRegion *irom = g_new(MemoryRegion, 1);
if (!qdev_realize(DEVICE(&s->cpu), NULL, errp)) {
return;
}
if (!sysbus_realize(SYS_BUS_DEVICE(&s->intc), errp)) {
return;
}
sysbusdev = SYS_BUS_DEVICE(&s->intc);
sysbus_mmio_map(sysbusdev, 0, memmap[INTC].base);
sysbus_connect_irq(sysbusdev, 0,
qdev_get_gpio_in(DEVICE(&s->cpu), ARM_CPU_IRQ));//把INTC的IRQ输出连到ARM核的IRQ输入引脚
sysbus_connect_irq(sysbusdev, 1,
qdev_get_gpio_in(DEVICE(&s->cpu), ARM_CPU_FIQ));
qdev_pass_gpios(DEVICE(&s->intc), dev, NULL);//把SoC中断控制器的中断输入传给主板的dev
//以便于后面的中断源直接连到板上,避免连中断控制器的麻烦
if (!sysbus_realize(SYS_BUS_DEVICE(&s->gptimer), errp)) {
return;
}
sysbusdev = SYS_BUS_DEVICE(&s->gptimer);
sysbus_mmio_map(sysbusdev, 0, memmap[GPT].base);
sysbus_connect_irq(sysbusdev, 0, qdev_get_gpio_in(dev, 5));
sysbus_connect_irq(sysbusdev, 1, qdev_get_gpio_in(dev, 6));
sysbus_connect_irq(sysbusdev, 2, qdev_get_gpio_in(dev, 7));
if (!sysbus_realize(SYS_BUS_DEVICE(&s->systimer), errp)) {
return;
}
sysbusdev = SYS_BUS_DEVICE(&s->systimer);
sysbus_mmio_map(sysbusdev, 0, memmap[SYST].base);
sysbus_connect_irq(sysbusdev, 0, qdev_get_gpio_in(dev, 17));
sysbus_create_simple("l2x0", memmap[PL310].base, NULL);
//...
}
在QEMU中,中断以QEMU GPIO的形式实现,一个设备可以提供gpio in(中断输入) 和 gpio out(中断输出)。SoC realize中要设置各设备的中断输出分别连到中断控制器的哪个输入上
注意:QEMU的中断号(gpio号)是从0开始的
QEMU-devices
实现一个设备的模拟支持便是要模拟软件对其MMIO寄存器的读写,同时模拟硬件的行为,做出对应的响应。
sprd-sc8810-intc.c
static void sprd_sc8810_intc_update(SC8810INTCState *s)
{
int irq, fiq;
irq = s->irq_raw_sts & s->irq_enable;
fiq = s->fiq_raw_sts & s->fiq_enable;
qemu_set_irq(s->parent_irq, !!irq); //设置 连接ARM IRQ的qemu gpio 状态
qemu_set_irq(s->parent_fiq, !!fiq);
}
static void sprd_sc8810_intc_set_irq(void *opaque, int irq, int level)
{
SC8810INTCState *s = opaque;
if (level) {
set_bit(irq, (void *)&s->irq_raw_sts);
set_bit(irq, (void *)&s->fiq_raw_sts);
} else {
clear_bit(irq, (void *)&s->irq_raw_sts);
clear_bit(irq, (void *)&s->fiq_raw_sts);
}
sprd_sc8810_intc_update(s);
}
static uint64_t sprd_sc8810_intc_read(void *opaque, hwaddr offset, unsigned size)
{
SC8810INTCState *s = opaque;
switch (offset) {
case INT_IRQ_MASK_STS:
return s->irq_raw_sts & s->irq_enable;
case INT_IRQ_RAW_STS:
return s->irq_raw_sts;
case INT_IRQ_ENABLE:
return s->irq_enable;
case INT_IRQ_DISABLE:
break;
case INT_IRQ_SOFT:
break;
case INT_IRQ_TEST_SRC:
/* Unimplemented */
break;
case INT_IRQ_TEST_SEL:
/* Unimplemented */
break;
case INT_FIQ_MASK_STS:
return s->fiq_raw_sts & s->fiq_enable;
case INT_FIQ_RAW_STS:
return s->fiq_raw_sts;
case INT_FIQ_ENABLE:
return s->fiq_enable;
case INT_FIQ_DISABLE:
break;
case INT_FIQ_SOFT:
break;
case INT_FIQ_TEST_SRC:
/* Unimplemented */
break;
case INT_FIQ_TEST_SEL:
/* Unimplemented */
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%x\n", __func__, (int)offset);
break;
}
return 0;
}
static void sprd_sc8810_intc_write(void *opaque, hwaddr offset, uint64_t value,
unsigned size)
{
SC8810INTCState *s = opaque;
switch (offset) {
case INT_IRQ_MASK_STS:
break;
case INT_IRQ_RAW_STS:
break;
case INT_IRQ_ENABLE:
s->irq_enable |= value;
break;
case INT_IRQ_DISABLE:
s->irq_enable &= ~value;
break;
case INT_IRQ_SOFT:
s->irq_raw_sts = (s->irq_raw_sts & 0xFFFFFFFD) | (value & 2);
break;
case INT_IRQ_TEST_SRC:
/* Unimplemented */
break;
case INT_IRQ_TEST_SEL:
/* Unimplemented */
break;
case INT_FIQ_MASK_STS:
break;
case INT_FIQ_RAW_STS:
break;
case INT_FIQ_ENABLE:
s->fiq_enable |= value;
break;
case INT_FIQ_DISABLE:
s->fiq_enable &= ~value;
break;
case INT_FIQ_SOFT:
s->fiq_raw_sts = (s->fiq_raw_sts & 0xFFFFFFFD) | (value & 2);
break;
case INT_FIQ_TEST_SRC:
/* Unimplemented */
break;
case INT_FIQ_TEST_SEL:
/* Unimplemented */
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"%s: Bad offset 0x%x\n", __func__, (int)offset);
break;
}
sprd_sc8810_intc_update(s);
}
static const MemoryRegionOps sprd_sc8810_intc_ops = {
.read = sprd_sc8810_intc_read,
.write = sprd_sc8810_intc_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
实现IRQ控制器时,对应sc8810的datasheet,软件读写时返回对应的值,保存中断mask状态,并适时更新IRQ状态,触发ARM中断。
同时为了实现虚拟机的挂起/恢复,迁移(虚拟机不关机,搬到另一个物理机上),需要实现设备的VMState
将你设备里需要保存、恢复的状态变量放在VMStateDescription 里就可以了,(有些类型,如qemu_irq,不需要放,也不能放)
static const VMStateDescription vmstate_sprd_sc8810_intc = {
.name = "sc8810.intc",
.version_id = 1,
.minimum_version_id = 1,
.fields = (VMStateField[]) {
VMSTATE_UINT32(irq_raw_sts, SC8810INTCState),
VMSTATE_UINT32(irq_enable, SC8810INTCState),
VMSTATE_UINT32(fiq_raw_sts, SC8810INTCState),
VMSTATE_UINT32(fiq_enable, SC8810INTCState),
VMSTATE_END_OF_LIST()
}
};
关于TImer
QEMU实现了一个ptimer API,但是ptimer只支持倒计时,也就只适用于倒计时的Timers。sc8810的generic timers是用ptimer模拟的,但是system timer因为没有正计时API,只能自己实现了。自己实现timer,需要用到qemu的几个clock?API,有取虚拟时间的(定时),还有取真实时间(RTC),可以到qemu的include里看看.
static void sprd_sc8810_systimer_reset(DeviceState *dev)
{
SC8810SYSTState *s = SPRD_SC8810_SYS_TIMER(dev);
s->alarm = 0xFFFF;
s->raw_irq_status = 0;
s->irq_enable = 0;
s->offset = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
timer_mod(s->timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + s->alarm);
}
static void sprd_sc8810_systimer_realize(DeviceState *dev, Error **errp)
{
SC8810SYSTState *s = SPRD_SC8810_SYS_TIMER(dev);
s->timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, sprd_sc8810_systimer_interrupt, s);
}
到此,QEMU上的sc8810模拟支持基本可以用来启动VLX和guests,并连接IDA 调试了
IDA动态调试
首先编译运行qemu,将VLX,modem,Linux加载到对应的地址,设置PC寄存器为VLX入口。
-S? 打开qemu自带的gdbstub,IDA可以连接上去,默认端口1234
-s 开机时暂停虚拟机
qemu-system-arm -M samsung-s7568 -vnc :0 -serial stdio -device loader,file=vmjaluna.bin,addr=0x400000,cpu-num=0 -device loader,file=modem.bin,addr=0x500000 -device loader,file=Image,addr=0x04508000 -monitor tcp::3333,server,nowait -S -s
可不加-S -s 调试器看看效果:
Red Bend VLX MH 4.1 Copyright (c) 2002-2011, Red Bend Software. All rights reserved.
NK: Processor revision r0p0 (ARMv7) NK: Cache is VIPT dcache line size 64 ?icache line size 64 NK: VFP is present NK: CRTX-A8 processor found NK: ?=== dump tag list at 0xc040012c === NK: ?tag = 0x54410001 ?size = 0x00000005 NK: ? ?core tag: ? ? ?0x00000000 0x00001000 0x00000000 //...... //...... NK: VM#4 command line: <> NK: VM#3 command line: < vram=512M show-guest-banks=0x4 no_console_suspend console=ttyNK vdev=(veth,0) vdev=(veth,1) vdev=(veth,2) viomem=* vdev=(vbpipe,0|a;256K) vdev=(vbpipe,2|a;32K)vdev=(vbpipe,3|a;4K) vdev=(vbpipe,4|a;1K) vdev=(vbpipe,5|a;1K) vdev=(vbpipe,6|a;32K) vdev=(vbpipe,7|a;32K) vdev=(vcons,>6|3) vdev=(vcons,6>|xlink=5) linux-timer=virtual vdev=(vclock_framework,>100) vdev=(vaudio,0>|1) root=/dev/ram0 rw init=/init > NK: VM#2 command line: < vram=21520K vdev=(vcons,5>|xlink=6) guest-reboot vdev=(vcons,>5) vdev=(vcons,>15) vdev=(veth,0) vdev=(veth,1) vdev=(veth,2) vdev=(vcons,15>) ?vdev=(vtimer,0>) vdev=(vbpipe,0|a;8K) vdev=(vbpipe,2|a;32K) vdev=(vbpipe,3|a;4K) vdev=(vbpipe,4|a;1K) vdev=(vbpipe,5|a;1K) vdev=(vbpipe,6|a;32K) vdev=(vbpipe,7|a;32K) vdev=(vtimer,>0) vdev=(vclock_framework,100>) vdev=(vaudio,>0)> NK: VM#1 command line: <> NK: NK command line: < vpark=(3) pmem=4M>
NK: nk_pmem_alloc initialized at [0xc1600000-0xc1a00000) NK: VM#3 uses only 0x0e63b000 < vram=0x20000000 0: ?0x00000000 ?0xc0000000 ? ? ?N ?[0x00000010] 2: ?0xc0000000 ?0x00400000 ? A ? ? [0x00020002] //...... NK: VLINK name <vcons> link 5 s_id 2 c_id 2 s_info <> c_info <xlink=6> NK: VLINK name <vcons> link 15 s_id 2 c_id 2 s_info <> c_info <> //...... NK: DEV ? class 0 id 4e4b494f NK: DEV * class 0 id 564c4e4b //...... NK: Embedded Core BSP used NK: SC8810G: hardware interrupt controller initialized NK: VPIC: config xirq=513 NK: SC8810G: no timer initialized NK: vevent_init: no vtimer vlinks found CBSP: console runs in polling mode NK: (1,0) starting VCPU 0xc0441040 (pc 0xc0408bb4 arch 0x7dd) [256] //启动CBSP guest NK: (2,0) starting VCPU 0xc0441ea0 (pc 0xc04ff000) [256] //启动modem OS guest NK: nk_pmem_alloc(0x1000) [0xc1600000-0xc1a00000) NK: nk_pmem_alloc(0x1000) [0xc1601000-0xc1a00000) ####: sc8800g_clock_nodes_init() passed! NKDDI: VPIC-FE initialized INT:unmask(10) failure : irq not connected NK: nk_pmem_alloc(0x40000) [0xc1602000-0xc1a00000) NK: nk_pmem_alloc(0x2000) [0xc1642000-0xc1a00000) VBPIPE: device vbpipe0 is created for OS#3 link=0 NK: nk_pmem_alloc(0x8000) [0xc1644000-0xc1a00000) NK: nk_pmem_alloc(0x8000) [0xc164c000-0xc1a00000) //...... VBPIPE: initialized NK: VPIC: -> intr_attach(5) NK: VPIC: -> intr_attach(7) Booting for the 0 time? runtime_nv_mem: 0,0,0,0,0,0,0,0,0,0 [_get_dynNV]fixed_nv_info.map_size:16? [_get_dynNV]ram_nv_table[0].mem:0xc0480000? [_get_dynNV]ram_nv_table[0].nr_sects:0x80? [_get_dynNV]runtime_nv_info.map_size:64? [_get_dynNV]ram_nv_table[1].mem:0xc04a0000? [_get_dynNV]ram_nv_table[1].nr_sects:0x200? [_get_dynNV]prod_param_info.map_size:1? [_get_dynNV]ram_nv_table[2].mem:0xc0490000? [_get_dynNV]ram_nv_table[2].nr_sects:0x3? Assert in file nvitem_dummy.c at line 32 info=[] //没加载NV,modem panic,调用AP Linux的modem dump vbpipe for assert open, waiting for peer open vbpipe for assert open, waiting for peer open NK: (3,0) starting VCPU 0xc0442d00 (pc 0xc4500000) [254]// 开始启动Linux guest [ ? ?0.000000] console [ttyNK0] enabled [ ? ?0.000000] pcpu-alloc: s0 r0 d32768 u32768 alloc=1*32768 [ ? ?0.000000] pcpu-alloc: [0] 0? [ ? ?0.000000] Built 1 zonelists in Zone order, mobility grouping on. ?Total pages: 58435 [ ? ?0.000000] Kernel command line: ?show-guest-banks=0x4 no_console_suspend Oonsole=ttyNK viomem=* linux-timer=virtual root=/dev/ram0 rw init=/init console=ttyS1,115200n8 loglevel=8 [ ? ?0.000000] PID hash table entries: 1024 (order: 0, 4096 bytes) [ ? ?0.000000] Dentry cache hash table entries: 32768 (order: 5, 131072 bytes) [ ? ?0.000000] Inode-cache hash table entries: 16384 (order: 4, 65536 bytes) [ ? ?0.000000] Memory: 0MB 0MB 230MB = 230MB total [ ? ?0.000000] Memory: 170412k/170412k available, 65344k reserved, 0K highmem [ ? ?0.000000] Virtual kernel memory layout: [ ? ?0.000000] ? ? vector ?: 0xffff0000 - 0xffff1000 ? ( ? 4 kB) [ ? ?0.000000] ? ? fixmap ?: 0xfff00000 - 0xfffe0000 ? ( 896 kB) [ ? ?0.000000] ? ? DMA ? ? : 0xffc00000 - 0xffe00000 ? ( ? 2 MB) [ ? ?0.000000] ? ? vmalloc : 0xd0800000 - 0xe0000000 ? ( 248 MB) [ ? ?0.000000] ? ? lowmem ?: 0xc0000000 - 0xd0000000 ? ( 256 MB) [ ? ?0.000000] ? ? pkmap ? : 0xbfe00000 - 0xc0000000 ? ( ? 2 MB) [ ? ?0.000000] ? ? modules : 0xbf000000 - 0xbfe00000 ? ( ?14 MB) [ ? ?0.000000] ? ? ? .init : 0xc4508000 - 0xc452e000 ? ( 152 kB) [ ? ?0.000000] ? ? ? .text : 0xc452e000 - 0xc4b1a0f8 ? (6065 kB) [ ? ?0.000000] ? ? ? .data : 0xc4b1c000 - 0xc4b7b2d0 ? ( 381 kB) [ ? ?0.000000] ? ? ? ?.bss : 0xc4b7b2f4 - 0xc4db7810 ? (2290 kB) [ ? ?0.000000] Preemptible hierarchical RCU implementation. [ ? ?0.000000] ?Verbose stalled-CPUs detection is disabled. [ ? ?0.000000] NR_IRQS:1024 NK: Using virtual PIC [ ? ?0.000000] sched_clock: 32 bits at 26MHz, resolution 38ns, wraps every 165191ms NK: VPIC: -> intr_attach(6) [ ? ?0.000000] Console: colour dummy device 80x30 [ ? ?0.007681] Calibrating delay loop... 695.50 BogoMIPS (lpj=3477504) [ ? ?0.284053] pid_max: default: 32768 minimum: 301 [ ? ?0.285764] Mount-cache hash table entries: 512 [ ? ?0.295286] CPU: Testing write buffer coherency: ok [ ? ?0.303002] hw perfevents: enabled with ARMv7 Cortex-A8 PMU driver, 5 counters available [ ? ?0.308849] L310 cache controller enabled [ ? ?0.311247] l2x0: 8 ways, CACHE_ID 0x410000c8, AUX_CTRL 0x1e560800, Cache size: 524288 B [ ? ?0.355362] print_constraints: dummy:? [ ? ?0.357845] NET: Registered protocol family 16 [ ? ?0.361950] sc8810_init_machine? [ ? ?0.370240] sc8810_add_misc_devices? [ ? ?0.371147] hw-breakpoint: debug architecture 0x4 unsupported. NK: VPIC: -> intr_attach(21) [ ? ?0.375434] request dma irq ok NK: VPIC: -> intr_attach(8) NK: VPIC: -> intr_attach(37) NK: VPIC: -> intr_attach(33) [ ? ?0.381447] print_constraints: LDO_VDDARM: 650 <--> 1300 mV at 650 mV normal standby [ ? ?0.382439] print_constraints: LDO_VDD25: 2500 <--> 3000 mV at 2500 mV normal standby [ ? ?0.383252] print_constraints: LDO_VDD18: 1200 <--> 2800 mV at 1800 mV normal standby [ ? ?0.384041] print_constraints: LDO_VDD28: 1800 <--> 3000 mV at 2800 mV normal standby [ ? ?0.384690] print_constraints: LDO_AVDDBB: 2800 <--> 3100 mV at 3000 mV normal standby [ ? ?0.385429] print_constraints: LDO_VDDRF0: 1800 <--> 2950 mV at 2850 mV normal standby [ ? ?0.386163] print_constraints: LDO_VDDRF1: 1800 <--> 2950 mV at 2850 mV normal standby [ ? ?0.386871] print_constraints: LDO_VDDMEM: 1800 mV normal? [ ? ?0.387422] print_constraints: LDO_VDDCORE: 650 <--> 1300 mV at 650 mV normal? [ ? ?0.388094] print_constraints: LDO_LDO_BG: 650 <--> 1300 mV at 650 mV normal? [ ? ?0.388702] print_constraints: LDO_AVDDVB: 2900 <--> 3400 mV at 3300 mV normal standby [ ? ?0.389408] print_constraints: LDO_VDDCAMDA: 1800 <--> 3000 mV at 2800 mV normal standby [ ? ?0.390124] print_constraints: LDO_VDDCAMD1: 1200 <--> 3300 mV at 2800 mV normal standby //......
接下来连接IDA
Debugger-Process options,配置好QEMU运行的主机ip和port,本机就填127.0.0.1
可以愉快的动态调试了
|