展讯SC8810平台虚拟机分析&在QEMU中模拟运行

前言

早在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

可以愉快的动态调试了

  • 19
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
展讯Spreadtrum MocorDroid 4.0.3 编译及客户化配置文档 本文主要介绍 Spreadtrum MocorDroid 4.0.3 编译命令、注意事项以及基于8810 的 4.0.3、 8825的 4.0.3 版本的客户化配置。即使是展讯平台其他的主芯片,也可以通过此文档来熟悉展讯平台的构架。 1. 简介................................................................................................................................................. 7 1.1 综述 .................................................................................................................................................... 7 1.2 基本内容 ............................................................................................................................................ 7 2. 编译环境......................................................................................................................................... 8 2.1 推荐配置 ............................................................................................................................................ 8 2.2 必须软件安装 .................................................................................................................................... 8 3. 获取展讯源码................................................................................................................................. 9 3.1 展讯的代码包目录结构..................................................................................................................... 9 3.2 idh 目录说明 ..................................................................................................................................... 11 3.3 Image 目录说明 ................................................................................................................................ 12 4. Mocordroid4.0.3 支持工程及差异 ............................................................................................. 15 4.1 8825 的 4.0.3 差异说明.................................................................................................................... 15 4.2 8810 的 4.0.3 差异说明.................................................................................................................... 16 5. 代码目录结构介绍 ....................................................................................................................... 17 5.1 整个代码目录结构........................................................................................................................... 17 5.2 客户需要修改配置的目录............................................................................................................... 18 6. 编译命令....................................................................................................................................... 19 6.1 4.0.3 编译系统的变化....................................................................................................................... 19 6.2 编译整个工程的方法....................................................................................................................... 19 6.2.1 lunch 操作................................................................................................................................ 19 6.2.2 choosecombo 操作................................................................................................................... 19 6.2.3 编译命令用法 ........................................................................................................................ 20 6.2.4 编译模块的命令..................................................................................................................... 20 6.3 编译 eng 版本的方法: ................................................................................................................... 21 7. 内核配置....................................................................................................................................... 23 7.1 基于图形界面方式配置内核........................................................................................................... 23 7.2 直接修改 defconfig 文件配置内核 ................................................................................................. 24 8. 客户化配置................................................................................................................................... 25 8.1 PRODUCT 与 DEVICE 的层次结构关系 ....................................................................................... 25 8.2 功能区分的层次与方法................................................................................................................... 27 8.2.1 功能区分层次 ........................................................................................................................ 27 8.2.2 功能区分方法 ........................................................................................................................ 27 8.3 新建项目的实例 .............................................................................................................................. 29 8.4 用 fork 脚本新建工程项目 .............................................................................................................. 32 8.4.1 fork 脚本位置及功能.............................................................................................................. 32 8.4.2 fork 脚本使用举例 .................................................................................................................. 33 9. 各模块配置方法........................................................................................................................... 35 9.1 PINMAP 的客户化配置 ................................................................................................................... 35 9.1.1 8825 的 PINMAP 配置 ........................................................................................................... 35 9.1.2 8810 的 PINMAP 配置 ........................................................................................................... 38 9.2 GPIO 客户化配置 ............................................................................................................................. 41 9.2.1 8825 的 GPIO 配置 ................................................................................................................. 41 9.2.2 8810 的 GPIO 配置 ................................................................................................................. 44 9.3 CLOCK 的客户化配置 ..................................................................................................................... 48 9.3.1 8825 CLOCK 配置 .................................................................................................................. 48 9.3.2 8810 CLOCK 配置 .................................................................................................................. 49 9.4 Regulator—LDO ............................................................................................................................... 52 9.4.1 8825 Regulator—LDO............................................................................................................. 52 9.4.2 8810 Regulator—LDO............................................................................................................. 53 9.5 LCD 客户化配置 .............................................................................................................................. 55 9.5.1 8825 LCD 配置 ....................................................................................................................... 55 9.5.2 8810 LCD 配置 ....................................................................................................................... 58 9.6 TP 客户化配置 .................................................................................................................................. 60 9.6.1 8825 TP 配置 ........................................................................................................................... 60 9.6.2 8810 TP 配置 ........................................................................................................................... 62 9.7 Keypad 客户化配置 .......................................................................................................................... 64 9.7.1 8825 Keypad 配置 ................................................................................................................... 64 9.7.2 8810 Keypad ............................................................................................................................ 66 9.8 G/P/L/M Sensor 客户化配置 ........................................................................................................... 69 9.8.1 8825 G/P/L/M Sensor 配置 ..................................................................................................... 69 9.8.2 8810 G/P/L/M Sensor 配置 ..................................................................................................... 70 9.9 Camera 驱动配置方法 ...................................................................................................................... 72 9.9.1 8825 Camera 配置 ................................................................................................................... 72 9.9.2 8810 Camera 配置 ................................................................................................................... 73 9.10 BT 配置 ........................................................................................................................................... 76 9.10.1 8825 BT 配置 ........................................................................................................................ 76 9.10.2 8810 BT 配置 ........................................................................................................................ 79 9.11 WIFI 配置 ........................................................................................................................................ 84 9.11.1 8825WIFI 配置 ...................................................................................................................... 84 9.11.2 8810 WIFI 配置 ..................................................................................................................... 85 9.12 Audio 配置....................................................................................................................................... 88 9.12.1 音频参数 .............................................................................................................................. 88 9.12.2 修改外部 pa.......................................................................................................................... 88 9.13 DDR 配置 ........................................................................................................................................ 90 9.13.1 U-boot .................................................................................................................................... 90 9.13.2 Kernel..................................................................................................................................... 90 9.14 EMMC 配置 .................................................................................................................................... 91 9.14.1 EMMC 分区表 ...................................................................................................................... 91 9.15 Nand 配置........................................................................................................................................ 92 9.16 SIM 配置 ......................................................................................................................................... 93 10. 应用模块配置方法....................................................................................................................... 94 10.1 多卡多待 客户化配置................................................................................................................... 94 10.2 第三方提供的 APK 客户化配置................................................................................................... 95 10.3 多国语言 客户化配置................................................................................................................... 95 11. 参考网站....................................................................................................................................... 96 11.1 U-Boot.............................................................................................................................................. 96 11.2 Kernel............................................................................................................................................... 96 11.3 Android ............................................................................................................................................ 96

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值