前言

早在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; // r4int v2; // r5v1 = &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; // r5int i; // r4char **v3; // r2void *result; // r0v1 = 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结构体,会传进CBSPv6->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; // lrNkOsCtx *v4; // r0int v5; // r1NkOsCtx *v6; // r10a1->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

可以愉快的动态调试了

展讯SC8810平台虚拟机分析在QEMU中模拟运行相关推荐

  1. 展讯6531平台socket

    最近公司接到一个新项目,搞展讯6531E平台的天气预报,基本流程其实也简单,就是手机端发送一个请求到服务器,然后服务器把数据发送回来,手机端接收并且解析就可以了,由于之前没有做过展讯socket相关的 ...

  2. 大屏幕服务器无信号,关于思讯互动平台大屏幕使用过程中错误的排查

    关于思讯互动平台大屏幕使用过程中 错误的排查 一.错误原因分析 原因具体内容 浏览器原因包括但不限于浏览器不兼容.浏览器设置出问题.浏览器安装了不明插件.浏览器有缓存 电脑原因电脑性能太差.开启了太多 ...

  3. MTK和高通展讯他们平台的主要区别是什么

    高通 美国的芯片厂商,世界顶级智能手机CPU厂商. MTK 台湾的芯片厂商,中文名 联发科,2009才推出智能手机芯片,中国山寨手机的芯片就是出自于它,称"山寨手机之父". 展讯 ...

  4. 展讯6500平台架构

    文章目录 框图 Modem软件架构 硬件原理图 硬件原理框架图 音频通路设计 Modem与 AP之间的功能流程 正常开机流程 正常关机流程 ASSERT流程 音频调试流程 下载流程 射频调试流程 Lo ...

  5. android 10.0 预制不可卸载app(RK 展讯 MTK平台都适用)

    1.概述 在10.0的系统产品进行定制化开发新手来说,对怎么预制apk感觉很陌生,但是也很简单的,可以把预制的apk放在vendor 目录下 也可以放在package/app下 配置好mk 让系统能编 ...

  6. 展讯android智能机平台FDL1,FDL2,SPL文件下载问题简析

    首先,我们要了解这样一个背景知识:展讯的每颗智能芯片(其他智能机平台应该也是如此)内部都有IROM和IRAM,IROM里有固化的Romcode(用于与PC端工具通讯,下载程序). 但是...... 但 ...

  7. 展讯平台-LCD驱动

    所谓驱动者,三分硬件,三分格式,四分软件.对于展讯平台的LCD驱动,首先就要了解一点基本的硬件知识. 一.LCD的接口 其实LCD的接口有很多,但是不管是在手机还是电脑,液晶屏的接口也最常用的有两个, ...

  8. 展讯走出困境开始爬坡

    2009年8月19日展讯发布Q2财报,虽然依然亏随1310万美元,不过从近几个季度财报分析,展讯已经走出2008年以来的低谷,开始迅速爬坡,按照展讯公布的财报预计2009年Q3营收将超过3100万美元 ...

  9. 展讯UIS8910FF

    目录 1. 产品简介 1.1. 文档约定 2.功能特性 2.1. 特性列表 2.2. 系统框架 3.物理接口 3.1. 管脚分布 1. 产品简介 展讯UIS8910FF系列模组是基于紫光展锐 LTE平 ...

  10. 移动开发-语音识别-调用讯飞平台提供的API

    1 登录讯飞平台,申请账号,创建一个应用 具体步骤可以百度查找 2 进入"我的应用",下载相应的SDK文件 选择语音听写(流式版)-> Android MSC在这里插入图片描 ...

最新文章

  1. 轻松记账工程冲刺第二阶段10
  2. multipart/form-data和application/x-www-form-urlencoded的区别
  3. ros订阅相机深度信息_一起做ROS-DEMO系列 (2):基于find_object_2d的目标匹配识别
  4. 通过数据库绑定的dropdownlist,如何让其第一条默认显示--请选择--
  5. 人工智能:一种现代方法汇总
  6. java调用so库中的native方法_Java如何调用本地.so库里的方法
  7. [置顶]IFTTT与Google+是什么?ifttt怎么玩?
  8. js排序算法06——希尔排序
  9. 图像锐化——基于梯度算子的五种方法
  10. 第二十四章——文件管理的代码保存
  11. 怎么知道自己是否适合计算机专业,事实:我怎么知道我的旧计算机是否适合win7或win10?...
  12. mysql 相同字段相减_mysql datetime 类型字段相减
  13. 好程序员web分享图片标签、绝对路径和相对路径
  14. 零基础学python大概要多久-零基础自学python要多久?
  15. 年薪最低十万签订协议
  16. 用XAML做网页!!—广告展示区
  17. 华三模拟器启动设备失败【启动设备MSR36-20_1失败】
  18. 识别不同域名访问不同主页
  19. JVM (二) 垃圾回收机制概念+垃圾回收器种类
  20. docker磁盘空间满了怎么清理

热门文章

  1. NCURSES程序设计之皇后问题
  2. mysql怎么解析json字符串_mysql解析json字符串
  3. android time计时器,android 计时器的三种实现(Chronometer、Timer、handler)
  4. GPS控制网技术设计、技术设计书、作业模式
  5. 姑娘留步,容我劫个色
  6. spring boot 报 http 406多种原因问题解决的总结
  7. 图片扫描纠偏java_Delphi 下 用 ImageEN 进行图像纠偏
  8. 测试9年,面试华为要薪1万,华为员工:公司没这么低工资的岗
  9. 组合模式-系统菜单的设计
  10. lumion功能介绍丨 建筑动画丨实时渲染