Vengineerの戯言

人生は短いけど、長いです。人生を楽しみましょう!

RISCVのQEMUのソースコードを眺めてみた

@Vengineerの戯言 : Twitter
SystemVerilogの世界へようこそすべては、SystemC v0.9公開から始まった 

はじめに

QEMUオープンソースのプロセッサエミュレータです。対応するプロセッサは、x86だけでなく、ARMやRISCVも対応しています。
www.qemu.org

Google君に "QEMU RISCV"で聞くと、RISCVのQEMUFedora 動かしました、とか、QEMU上でRISCVのLinuxを動かすのがほとんどでした。

今回は、動かすことよりも、いつものようにRISCVの部分のソースコードの中身を眺めて、どんな感じになっているを探っていきます。

QEMUのRISCVのドキュメント

まずは、QEMUのサイトにあるRISCVのドキュメントを眺めてみました。

QEMUのビルド

github.com の QEMU のページを clone してから、

RISCVの64ビットなら

 ./configure --target-list=riscv64-softmmu && make

を実行すると、qemu-system-riscv64 ができます。

RISCVの32ビットなら

 ./configure --target-list=riscv32-softmmu && make

を実行すると、qemu-system-riscv64 ができます。

一応、64ビットと32ビットは別物です。

64ビット Fedora を使ってブートする

Fedoraをブートするのが目的ではなく、qemu-system-risc64 コマンドの引数を確認するためです。

  qemu-system-riscv64 \
   -nographic \
   -machine virt \
   -smp 4 \
   -m 2G \
   -kernel Fedora-Minimal-Rawhide-*-fw_payload-uboot-qemu-virt-smode.elf \
   -bios none \
   -object rng-random,filename=/dev/urandom,id=rng0 \
   -device virtio-rng-device,rng=rng0 \
   -device virtio-blk-device,drive=hd0 \
   -drive file=Fedora-Minimal-Rawhide-20200108.n.0-sda.raw,format=raw,id=hd0 \
   -device virtio-net-device,netdev=usernet \
   -netdev user,id=usernet,hostfwd=tcp::10000-:22

各オプションについて簡単に説明します。

  • nographic : テキストのコンソールだけを使って、グラフィックスを使わない
  • machine virt : Fedoraを動作させるマシンのタイプをしていする。ここでは、virt というマシンを使う
  • smp 4 : CPUコア数をしていする。ここでは、4 個使う
  • m 2G : システムメモリのサイズを指定する。2G なので、2Gバイト
  • kernel Fedora-Minimal-Rawhide-*-fw_payload-uboot-qemu-virt-smode.elf : 実行するカーネル (プログラム)を指定する。
  • bios none : BIOS を指定する。ここでは、BIOS は無いものとする
  • object rng-random,filename=/dev/urandom,id=rng0 : rng-random(ランダム生成)のオブジェクトを指定する
  • device virtio-rng-device,rng=rng0 : virtio-rng-device (仮想ランダム生成デバイス)を指定する。ここでは、上で設定した rng0 を指定する
  • device virtio-blk-device,drive=hd0 : virtio-blk-device (仮想ブロックデバイス)を指定する。ここでは、hd0 を指定する
  • drive file=Fedora-Minimal-Rawhide-20200108.n.0-sda.raw,format=raw,id=hd0 : ドライブを指定する。file= にはファイル名、format= にはファイルのフォーマット、id= に識別子(hd0)を指定する。この識別子で指定した hd0 が 上の virtio-blk-device で指定した hd0 になる
  • device virtio-net-device,netdev=usernet : vrtio-net-device (仮想ネットワークデバイス)を指定する。ここでは usernet を指定する
  • netdev user,id=usernet,hostfwd=tcp::10000-:22 : ネットワークデバイスを指定する。user とし、id=usernet で識別する。ここで指定した usernet は virtio-net-device の netdevで指定した usernet になる。hostfwd= にして使用するネットワークのポート番号(tcp:10000-:22) を指定する
  • machine オプションで指定するのがプログラムが動くハードウェア構成、簡単に言うとボード(基板)のことです。ここでは、virt が使われることになります。次にこの virt の中身を見ていきます。

RISC-V System emulatorについてのドキュメントに、RISC-Vでの -machine (-M) についての説明もあります。

virt の中身を見てみる

QEMU(qemu-system-risc64)起動時のオプション -machine オプションで指定した virt はソースコードのどこのどのファイルで定義されているのでしょうか?

hw/risc の下の virt.c で、次のようになっています。

static const TypeInfo virt_machine_typeinfo = {
    .name       = MACHINE_TYPE_NAME("virt"),
    .parent     = TYPE_MACHINE,
    .class_init = virt_machine_class_init,
    .instance_init = virt_machine_instance_init,
    .instance_size = sizeof(RISCVVirtState),
};

Typeinfo 構造体 の name に MACHINE_TYPE_NAME("virt") で指定しています。これが、-machine オプション で指定できるものです。この定義の後に、次のようなコードがあり、qemu-system-riscv64 を実行した最初に virt が -machine オプションとして使用できるようになっています。

static void virt_machine_init_register_types(void)
{
    type_register_static(&virt_machine_typeinfo);
}

type_init(virt_machine_init_register_types)

指定された virt に対して最初に実行されるのは、class_init に指定された 下記の virt_machine_class_init 関数です。その後に、instance_int に指定された virt_machine_instance_init 関数が実行されます。
virt_machine_class_init 関数では、MachineClass変数にいろいろと設定しています。virt_machine_instance_init 関数は何もやっていません。

static void virt_machine_instance_init(Object *obj)
{
}

static void virt_machine_class_init(ObjectClass *oc, void *data)
{
    MachineClass *mc = MACHINE_CLASS(oc);

    mc->desc = "RISC-V VirtIO board";
    mc->init = virt_machine_init;
    mc->max_cpus = VIRT_CPUS_MAX;
    mc->default_cpu_type = TYPE_RISCV_CPU_BASE;
    mc->pci_allow_0_address = true;
    mc->possible_cpu_arch_ids = riscv_numa_possible_cpu_arch_ids;
    mc->cpu_index_to_instance_props = riscv_numa_cpu_index_to_props;
    mc->get_default_cpu_node_id = riscv_numa_get_default_cpu_node_id;
    mc->numa_mem_supported = true;
}

次に、mc->init に指定された関数 (virt_machine_init) が実行されます。この virt_machine_init 関数では仮想的なボードを構築していきます。

最初の部分でいろいろなメモリ領域を割り当てています。後で使う start_addr には DRAMの先頭アドレス(memmap[VIRT_DRAM].base (0x80000000))を設定しています。
その後のVIRT_SOCKETS_MAX の if 文では、QEMU起動時に指定したソケット数(チップ数、デフォルトでは 1 だと思います)のチェックをしています。

static void virt_machine_init(MachineState *machine)
{
    const MemMapEntry *memmap = virt_memmap;
    RISCVVirtState *s = RISCV_VIRT_MACHINE(machine);
    MemoryRegion *system_memory = get_system_memory();
    MemoryRegion *main_mem = g_new(MemoryRegion, 1);
    MemoryRegion *mask_rom = g_new(MemoryRegion, 1);
    char *plic_hart_config, *soc_name;
    size_t plic_hart_config_len;
    target_ulong start_addr = memmap[VIRT_DRAM].base;
    target_ulong firmware_end_addr, kernel_start_addr;
    uint32_t fdt_load_addr;
    uint64_t kernel_entry;
    DeviceState *mmio_plic, *virtio_plic, *pcie_plic;
    int i, j, base_hartid, hart_count;

    /* Check socket count limit */
    if (VIRT_SOCKETS_MAX < riscv_socket_count(machine)) {
        error_report("number of sockets/nodes should be less than %d",
            VIRT_SOCKETS_MAX);
        exit(1);
    }

次の for 文で各ソケットに対する初期化を行っています。結構長いです。この部分では割り込み関連の設定をしています。コメントで説明を追加します。

    /* Initialize sockets */
    mmio_plic = virtio_plic = pcie_plic = NULL;
    for (i = 0; i < riscv_socket_count(machine); i++) {
        if (!riscv_socket_check_hartids(machine, i)) {
            error_report("discontinuous hartids in socket%d", i);
            exit(1);
        }

        base_hartid = riscv_socket_first_hartid(machine, i);
        if (base_hartid < 0) {
            error_report("can't find hartid base for socket%d", i);
            exit(1);
        }

        hart_count = riscv_socket_hart_count(machine, i);
        if (hart_count < 0) {
            error_report("can't find hart count for socket%d", i);
            exit(1);
        }

        soc_name = g_strdup_printf("soc%d", i);                                                      // soc%d の %d に ソケットの番号が入り、名前になります。
        object_initialize_child(OBJECT(machine), soc_name, &s->soc[i],
                                TYPE_RISCV_HART_ARRAY);
        g_free(soc_name);
        object_property_set_str(OBJECT(&s->soc[i]), "cpu-type",
                                machine->cpu_type, &error_abort);
        object_property_set_int(OBJECT(&s->soc[i]), "hartid-base",
                                base_hartid, &error_abort);
        object_property_set_int(OBJECT(&s->soc[i]), "num-harts",
                                hart_count, &error_abort);
        sysbus_realize(SYS_BUS_DEVICE(&s->soc[i]), &error_abort);

        /* Per-socket CLINT */                                                                                    // 割り込みコントローラの設定。SiFive の割り込みコントローラを使っています。
        sifive_clint_create(
            memmap[VIRT_CLINT].base + i * memmap[VIRT_CLINT].size,
            memmap[VIRT_CLINT].size, base_hartid, hart_count,
            SIFIVE_SIP_BASE, SIFIVE_TIMECMP_BASE, SIFIVE_TIME_BASE,
            SIFIVE_CLINT_TIMEBASE_FREQ, true);

        /* Per-socket PLIC hart topology configuration string */
        plic_hart_config_len =
            (strlen(VIRT_PLIC_HART_CONFIG) + 1) * hart_count;
        plic_hart_config = g_malloc0(plic_hart_config_len);
        for (j = 0; j < hart_count; j++) {
            if (j != 0) {
                strncat(plic_hart_config, ",", plic_hart_config_len);
            }
            strncat(plic_hart_config, VIRT_PLIC_HART_CONFIG,
                plic_hart_config_len);
            plic_hart_config_len -= (strlen(VIRT_PLIC_HART_CONFIG) + 1);
        }

        /* Per-socket PLIC */                                                                                     // PLIC : [https://qiita.com/tkato/items/80abce474f329676acb3:title=Platform-Level Interrupt Controller] の設定
        s->plic[i] = sifive_plic_create(
            memmap[VIRT_PLIC].base + i * memmap[VIRT_PLIC].size,
            plic_hart_config, base_hartid,
            VIRT_PLIC_NUM_SOURCES,
            VIRT_PLIC_NUM_PRIORITIES,
            VIRT_PLIC_PRIORITY_BASE,
            VIRT_PLIC_PENDING_BASE,
            VIRT_PLIC_ENABLE_BASE,
            VIRT_PLIC_ENABLE_STRIDE,
            VIRT_PLIC_CONTEXT_BASE,
            VIRT_PLIC_CONTEXT_STRIDE,
            memmap[VIRT_PLIC].size);
        g_free(plic_hart_config);

        /* Try to use different PLIC instance based device type */
        if (i == 0) {
            mmio_plic = s->plic[i];
            virtio_plic = s->plic[i];
            pcie_plic = s->plic[i];
        }
        if (i == 1) {
            virtio_plic = s->plic[i];
            pcie_plic = s->plic[i];
        }
        if (i == 2) {
            pcie_plic = s->plic[i];
        }
    }

次はメモリ関連の設定を行っています。PCIEメモリに関しては、32bit かどうかで if文で分岐しています。その後に riscv_virt_board.ram がメインメモリになり、memmap[VIRT_DRAM].base (0x80000000) がメインメモリの開始アドレスになります。

    if (riscv_is_32bit(&s->soc[0])) {
#if HOST_LONG_BITS == 64
        /* limit RAM size in a 32-bit system */
        if (machine->ram_size > 10 * GiB) {
            machine->ram_size = 10 * GiB;
            error_report("Limiting RAM size to 10 GiB");
        }
#endif
        virt_high_pcie_memmap.base = VIRT32_HIGH_PCIE_MMIO_BASE;
        virt_high_pcie_memmap.size = VIRT32_HIGH_PCIE_MMIO_SIZE;
    } else {
        virt_high_pcie_memmap.size = VIRT64_HIGH_PCIE_MMIO_SIZE;
        virt_high_pcie_memmap.base = memmap[VIRT_DRAM].base + machine->ram_size;
        virt_high_pcie_memmap.base =
            ROUND_UP(virt_high_pcie_memmap.base, virt_high_pcie_memmap.size);
    }

    /* register system main memory (actual RAM) */
    memory_region_init_ram(main_mem, NULL, "riscv_virt_board.ram",
                           machine->ram_size, &error_fatal);
    memory_region_add_subregion(system_memory, memmap[VIRT_DRAM].base,
        main_mem);

次は Device Tree と Kernel 関連です。最初に、create_fdt 関数(この関数は後で詳しく見ていきます)でデバイスドライバを作ります。QEMU起動時のオプションで指定した -m オプションのメモリサイズは machine->ram_size に設定されています。machine->kernel_cmdline は Linux (Kernel) の コマンドラインを指定した時の値になります。

BOOT Rom を mask_rom (riscv_virt_board.mrom) に設定します。先頭アドレスはVIRT_MROM(0x1000), サイズ(0xf000) になります。
その後に、Firmware (32ビットの場合は、opensbi-riscv32-generic-fw_dynamic.bin を、64ビットの場合は、opensbi-riscv64-generic-fw_dynamic.bin )を start_addr (DRAMの先頭アドレス:0x80000000)に設定し、firmware_end_addr までの領域が firmware が使用するメモリ領域なります)。
machine->kernel_filename が設定されていたら、firmware_end_addr から kernel_start_addr を計算しなおして、ここにカーネルを設定します。machine->initrd_filenameが設定されたら、initrd の設定をします。

drive_get関数の戻り値)Pflashがある場合)によって、start_addr をDRAMからFLASHの先頭アドレス(virt_memmap[VIRT_FLASH].base = 0x20000000)変更しています。

そして、DRAMにロードする fdt (Device Treeのデータ)をロードするアドレスを再計算して、fdt_load_addr に設定します。

    /* create device tree */
    create_fdt(s, memmap, machine->ram_size, machine->kernel_cmdline,
               riscv_is_32bit(&s->soc[0]));

    /* boot rom */
    memory_region_init_rom(mask_rom, NULL, "riscv_virt_board.mrom",
                           memmap[VIRT_MROM].size, &error_fatal);
    memory_region_add_subregion(system_memory, memmap[VIRT_MROM].base,
                                mask_rom);

    if (riscv_is_32bit(&s->soc[0])) {
        firmware_end_addr = riscv_find_and_load_firmware(machine,
                                    "opensbi-riscv32-generic-fw_dynamic.bin",
                                    start_addr, NULL);
    } else {
        firmware_end_addr = riscv_find_and_load_firmware(machine,
                                    "opensbi-riscv64-generic-fw_dynamic.bin",
                                    start_addr, NULL);
    }

    if (machine->kernel_filename) {
        kernel_start_addr = riscv_calc_kernel_start_addr(&s->soc[0],
                                                         firmware_end_addr);

        kernel_entry = riscv_load_kernel(machine->kernel_filename,
                                         kernel_start_addr, NULL);

        if (machine->initrd_filename) {
            hwaddr start;
            hwaddr end = riscv_load_initrd(machine->initrd_filename,
                                           machine->ram_size, kernel_entry,
                                           &start);
            qemu_fdt_setprop_cell(s->fdt, "/chosen",
                                  "linux,initrd-start", start);
            qemu_fdt_setprop_cell(s->fdt, "/chosen", "linux,initrd-end",
                                  end);
        }
    } else {
       /*
        * If dynamic firmware is used, it doesn't know where is the next mode
        * if kernel argument is not set.
        */
        kernel_entry = 0;
    }

    if (drive_get(IF_PFLASH, 0, 0)) {
        /*
         * Pflash was supplied, let's overwrite the address we jump to after
         * reset to the base of the flash.
         */
        start_addr = virt_memmap[VIRT_FLASH].base;
    }

    /* Compute the fdt load address in dram */
    fdt_load_addr = riscv_load_fdt(memmap[VIRT_DRAM].base,
                                   machine->ram_size, s->fdt);

リセットがかかったときの設定を riscv_setup_room_reset_vec 関数によって、virt_memmap[VIRT_MROM.base]= 0x1000)に設定しています。

    /* load the reset vector */
    riscv_setup_rom_reset_vec(machine, &s->soc[0], start_addr,
                              virt_memmap[VIRT_MROM].base,
                              virt_memmap[VIRT_MROM].size, kernel_entry,
                              fdt_load_addr, s->fdt);

次のIOの設定を行っています。

  • VIRT_TEST
  • VIRT_VIRTIO
  • VIRT_PCIE_ECAM
  • VIRT_PCIE_MMIO
  • VIRT_UART0
  • VIRT_RTC
  • PFlash

VIRT_VIRTIO では、割り込みの設定も行っています (VIRTIO_IRQ + i)

    /* SiFive Test MMIO device */
    sifive_test_create(memmap[VIRT_TEST].base);

    /* VirtIO MMIO devices */
    for (i = 0; i < VIRTIO_COUNT; i++) {
        sysbus_create_simple("virtio-mmio",
            memmap[VIRT_VIRTIO].base + i * memmap[VIRT_VIRTIO].size,
            qdev_get_gpio_in(DEVICE(virtio_plic), VIRTIO_IRQ + i));
    }

    gpex_pcie_init(system_memory,
                   memmap[VIRT_PCIE_ECAM].base,
                   memmap[VIRT_PCIE_ECAM].size,
                   memmap[VIRT_PCIE_MMIO].base,
                   memmap[VIRT_PCIE_MMIO].size,
                   virt_high_pcie_memmap.base,
                   virt_high_pcie_memmap.size,
                   memmap[VIRT_PCIE_PIO].base,
                   DEVICE(pcie_plic));

    serial_mm_init(system_memory, memmap[VIRT_UART0].base,
        0, qdev_get_gpio_in(DEVICE(mmio_plic), UART0_IRQ), 399193,
        serial_hd(0), DEVICE_LITTLE_ENDIAN);

    sysbus_create_simple("goldfish_rtc", memmap[VIRT_RTC].base,
        qdev_get_gpio_in(DEVICE(mmio_plic), RTC_IRQ));

    virt_flash_create(s);

    for (i = 0; i < ARRAY_SIZE(s->flash); i++) {
        /* Map legacy -drive if=pflash to machine properties */
        pflash_cfi01_legacy_drive(s->flash[i],
                                  drive_get(IF_PFLASH, 0, i));
    }
    virt_flash_map(s, system_memory);
}

create_fdt 関数

create_fdt 関数は、Device tree を作る関数です。Linuxを起動するときは、Kernel ファイルだけでなく、コンパイルしたDevice treeのファイル (テキストフォーマットの dts ファイルを dtc コマンドにてコンパイルして、dtb ファイルに変換したもの)を指定します。RISCVの virt ではファイルが指定された場合はそのファイルから Device tree を取り込みますが、ファイルが指定されなかった場合はコンパイルした Device tree をこの create_fdt 関数で作っています。

ファイルを指定した場合、load_device_tree 関数でファイルを読み込み、成功したら、goto update_bootargs で create_fdt 関数のほぼ最後にジャンプします。ファイルを指定しない場合は、create_devie_tree 関数で Device tree の大本を作って、その後に地道に virt 用の Device tree を作ることになります。

    if (mc->dtb) {
        fdt = s->fdt = load_device_tree(mc->dtb, &s->fdt_size);
        if (!fdt) {
            error_report("load_device_tree() failed");
            exit(1);
        }
        goto update_bootargs;
    } else {
        fdt = s->fdt = create_device_tree(&s->fdt_size);
        if (!fdt) {
            error_report("create_device_tree() failed");
            exit(1);
        }
    }

最後に、cmdline )machine->kernel_cmdline) が指定された場合は、"/chosen" を設定しています。

update_bootargs:
    if (cmdline) {
        qemu_fdt_setprop_string(fdt, "/chosen", "bootargs", cmdline);
    }
}

virt 以外の machine はどんなものがあるのか?

ここ に あります。

spike は、virt と同じような構成でしたが、実機がある microchip_pfsoc, opentitan, sifive_e/u に関しては違う構成でした。

下記は、opentitan.c の TypeInfo の部分ですが、class_init 関数に指定した lowrisc_ibex_soc_class_init 関数は下記のようになっています。opentitan.c, sifive_e.c, sifive_u.c でも同じようです。 instance_init 関数に指定した lowrisc_ibex_soc_init 関数でいろいろやっているみたいです。

static void lowrisc_ibex_soc_class_init(ObjectClass *oc, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(oc);

    dc->realize = lowrisc_ibex_soc_realize;
    /* Reason: Uses serial_hds in realize function, thus can't be used twice */
    dc->user_creatable = false;
}

static const TypeInfo lowrisc_ibex_soc_type_info = {
    .name = TYPE_RISCV_IBEX_SOC,
    .parent = TYPE_DEVICE,
    .instance_size = sizeof(LowRISCIbexSoCState),
    .instance_init = lowrisc_ibex_soc_init,
    .class_init = lowrisc_ibex_soc_class_init,
};

どうやら、QOM (The QEMU Object Model) について、学ばないといけないようです。

QOM (The QEMU Object Model) とは、

ここ にドキュメントがありました。英語を読むのが面倒なので、Google翻訳にお任せしました。

クラスの初期化
オブジェクトを初期化する前に、オブジェクトのクラスを初期化する必要があります。遅延して作成されるすべてのインスタンスオブジェクトに対して、クラスオブジェクトは1つだけです。

クラスは、最初に親クラスを初期化することによって初期化されます(必要な場合)。親クラスオブジェクトが初期化されると、それは現在のクラスオブジェクトにコピーされ、クラスオブジェクト内の追加のストレージはゼロで埋められます。

この効果により、クラスは、親クラスがすでに初期化した仮想関数ポインターを自動的に継承します。他のすべてのフィールドはゼロで埋められます。

すべての親クラスが初期化されると、#TypeInfo :: class_initが呼び出され、インスタンス化されるクラスが仮想関数のデフォルトの初期化を提供できるようになります。上記の例を変更して、オーバーライドされた仮想関数を導入する方法を次に示します。

新しい仮想メソッドを導入するには、クラスが独自の構造体を定義し、.class_sizeメンバーを#TypeInfoに追加する必要があります。各メソッドには、簡単に呼び出すためのラッパー関数もあります。

よくわからないです。。。

終わりに

今回は、RISCVのQEMUソースコードを眺めてみました。

QEMUは実機としてのハードウェアが無くても、Linuxなどのプログラムが動かせるとっても便利なソフトウエアです。
QEMUを使ってプログラムを動かすだけでなく、今回見てきた部分を変えることで新しいハードウェアにも対応できます。半導体開発はとっても時間とお金がかかりますが、QEMUを使うことで時間もそんなにかからないし、お金もほとんどかからないで新しいハードウェアができてしまいます。

おまけに、下記のツイートに書いたように、XilinxQEMUを使うと、SystemC + Verilator にて Verilog HDL で書いたハードウェアも利用することができますよ。

おしまい。

2021年2月の映画鑑賞

映画好きの戯言

2月、25本、アマゾン100円(10本)

1月:26本、アマゾン100円(5本)


シライサン 2019、アマゾン、100円
飯豊まりえ主演、シライサンの作りがいまいち。。。

罪の余白 2015
吉本実憂の女子高校生が怖かったわ。。。

引っ越し大名! 2019、アマゾン、100円
主演は星野源だが、共演の高橋一生濱田岳もよかった。高畑充希の旦那はどうなったんだ。

ダウト~嘘つきオトコは誰?~ 2019
男子年収1000万円以上の婚活パーティーに参加できて、かつ、10人からカード貰えるのって、奇跡に近いでしょうよ。
主演は、堀田茜。3年A組 ―今から皆さんは、人質です―2019TVに出てたんだって。

少女は悪魔を待ちわびて
(2016) / MISSING YOU
殺された班長は、半地下に住んでいると。あるので、半地下は普通なんだ。
韓国のビルの屋上は緑に塗られている。。。

やわらかい生活 2005
寺島しのぶ主演の映画だからね。豊川悦司が普通の兄ちゃんだった。

FLU 運命の36時間 (2013) / THE FLU

累 -かさね- 2018
土屋太鳳、芳根京子がいい味だしている。

かぐや様は告らせたい ~天才たちの恋愛頭脳戦~ (2019)
橋本環奈、超お金持ちのお嬢様。。笑えるし。。。

小説の神様 君としか描けない物語 (2020)
橋本環奈、ちょっとSっぽい。

本能寺ホテル (2017)
観たと思っていたが、「プリンセス トヨトミ」と勘違いしていた。監督と綾瀬はるかと堤堤真一は、同じだけど。

Sylvie's Love
Amazon Original、主演のTessa Thompson  メン・イン・ブラック:インターナショナル (2019)のエージェントMだよ。

ガール・イン・ザ・ミラー (2018) / LOOK AWAY
原題とかなり違う邦題。主演インディア・アイズリーアンダーワールド 覚醒 2012 に出てた

ジュマンジ/ウェルカム・トゥ・ジャングル (2017) / JUMANJI: WELCOME TO THE JUNGLE、アマゾン、100円。
続編のジュマンジ/ネクスト・レベル (2019) / JUMANJI: THE NEXT LEVEL を先に観ちゃったので、こちらも。
カレン・ギラン / Karen Gillan、やっと思い出したよ。Selfie の主演。allcinema には載っていない。なぜなら、日本では未公開なドラマなので。。。

サイレントヒル (2006) / SILENT HILL
昔(2007年にDVDで観たことになっている)が、最初の数十分しか覚えていなかった。

サイレントヒル:リベレーション3D (2012) / SILENT HILL: REVELATION 3D
母親役は出てこない。父親役と子供。何故か?教団のメンバーが狂暴になっている。

ティーンスピリット (2018) / TEEN SPIRIT、アマゾン、100円。
エル・ファニング主演の青春&スターになる物語。 エル・ファニング結構地味な役やっているけど、まー、美しいよね。

透明人間 (2020) / THE INVISIBLE MAN、アマゾン、100円。
サスペンスになっていた。主人公のエリザベス・モス、アスに出てた

感染家族 (2019) / THE ODD FAMILY: ZOMBIE ON SALE、アマゾン、100円。
ゾンビにかまれて、若返るということを知った家族がビジネスを始め儲かったが、その後、噛まれた人たちがゾンビになっちゃうというお話。
最後は最初に噛まれた父がハワイから返ってきて、父がゾンビになった人を噛んで、元に戻すことを始めるというお話。コメディだよ。。。韓国映画

愛がなんだ 2018、アマゾン、100円。
主演の岸井ゆきの成田凌より、脇役の江口のりこが今までの江口のりこっぽくなくてよかった。深川麻衣はまあまま

バッド・ジーニアス 危険な天才たち (2017) / CHALARD GAMES GOENG
タイ映画。高校生が普通に高校生だった。裕福な家庭の子供たちが行く高校だからなのだろうか。。。ホテルを経営している親の息子がそのホテルのプールに入っているシーンとかあったし。

裏切りのサーカス (2011) / TINKER TAILOR SOLDIER SPY
冷戦時代の英国MI6とソ連KGBのスパイ合戦のお話。MI6側の裏切り者は誰だ?

トールキン 旅のはじまり (2019) / TOLKIEN、アマゾン、100円
指輪物語ホビットの原作者の若い時のお話。オックスフォード大学の中庭の芝生、学生は横切れないよ。

マザーレス・ブルックリン (2019) / MOTHERLESS BROOKLYN、アマゾン、100円
1950年代のアメリカの探偵のお話。結構渋かった。

ブライトバーン/恐怖の拡散者 (2019) / BRIGHTBURN、アマゾン、100円
少年だけど、強力な力を身に付けると悪になるんだね。

Thunderbolt 4.0 って、何だ? Apple M1機の Thunderbolt 4.0 の実装はどうなっている?

@Vengineerの戯言 : Twitter
SystemVerilogの世界へようこそすべては、SystemC v0.9公開から始まった 

はじめに

Thunderbolt。今までは、AppleMacにしか付いていなくて全く興味はなかったのですが、Apple M1も Thunderbolt 4サポートしているということでいろいろと調べてみました。
また、昨年発表があったIntelTiger LakeではCPU側に Thunderbolt 4が搭載されているのを最近知りました。Tiger Lakeより前のチップセットでは、Thunderbolt 3は CPU側ではなく、I/O側(PCH)に付いていました。Thunderbolt が速いと言っても、PCH側についていると CPUとPCHとのインターフェースである DMI 3.0 がボトルネックで性能出るの?って感じでした。DMI 3.0 って、PCIe Gen3 x4 相当の性能なので、PCH側にいっぱい付いている USB 3.0 や PCIe 3.0 のポートが働き始めると Thunderbolt 3 の性能でないじゃん?と思っていた次第です。Tiger Lake では、CPU側に Thunderbolt 4 が付いたことにより、DMI 3.0 の制約を受けることなくシステムのメモリにアクセスできるようになったわけです。

ちょっと話がずれますが、下図にあるように Tiger Lake (下図は Intel のサイトにある図の引用です)では、Thunderbolt 4 がCPU側に付いただけでなく、PCIe Gen4 x4 を追加されました。これにより、Thunderbolr 3 と同じように PCH側にあった PCIe Gen 3 に接続していた M.2 の SSD を CPU側に接続することができるようになり、PCIe Gen4 対応の M.2 の SSD の性能が引き出せるようになったわけです。

https://www.intel.co.jp/content/dam/www/program/design/us/en/images/16x9/11th-generation-core-processor.png

上の図では、PCIe Gen4 1x4 となっているので、x1 が 4個なのか、x4 が 1個なのが分からないので他のものを調べてみたら、こちらの記事(Intel Tiger Lake UP3 COM Express Module Offers High AI Performance, PCIe Gen4 Interface)には以下の図が載っていますので引用します。この図では、1 PCIe Gen4 x4 となっているので、PCIe Gen4 は、x4 が 1個追加されたことが分かります。

https://www.cnx-software.com/wp-content/uploads/2020/09/Tiger-Lake-UP3-COM-Express-Block-Diagram.jpg.webp

Thunderbolt って何?

Wikipedia によると、

Thunderbolt(サンダーボルト)は、インテルがアップルと共同開発した[1]高速汎用データ伝送技術である。

とありますね。Intel が開発して、Appleが使っているとばっかり思っていましたが、共同開発したんですね。

Light Peak(ライト ピーク)を基にして開発されたコンピュータに周辺機器を接続するためのシリアルバス規格の1つで、技術的にPCI ExpressとDisplayPortを基盤としている。

ともあります。そして、右側の全モデルが IEEE 1394 であると、これを見て、あーーーーー、そこだったんだね。って思った次第です。。。

FireWireの後継として開発されたインターフェースで、ホスト機器にさまざまな周辺機器を接続するためのバス規格である。同時期に登場した高速汎用外部バス規格USB 3.0と比べ、FireWire同様にデイジーチェーンをサポートするなどUSBより多機能かつ高性能である一方、ホスト側・デバイス側の両方にインテル製のモジュールが必要であり、ホスト側はコントローラ機能の一部が2019年発売のIce Lake世代からモバイル向けCPUには統合されたものの、依然としてUSBに比べ高コストである。

デイジーチェーンができるのも、今回調べて初めて知りました。。。

ホスト側・デバイス側の両方にインテル製のモジュールが必要であり、ホスト側はコントローラ機能の一部が2019年発売のIce Lake世代からモバイル向けCPUには統合されたものの、依然としてUSBに比べ高コストである。

にあるように、Apple M1機には Intel の Thunderbolt 4 のチップが2個搭載されています。上記で説明したように Intelチップセットには Thunderbolt が載っています。USBに比べて高コストなのはIntelのチップが必要なだけでなく、ケーブルも USB 3.0 用のものと比べて高いです。。。

Thunderbolt 1 と 2 の時は、PCI Express と DisplayPort をサポートし、コネクタは独自でしたが、Thunderbolt 3 ではUSB 3.1 Gen2 をサポートし、USB Type-C のコネクタを使うようになり、一見、USB と区別がつきにくいですが、マークがちょっと違います。Thunderbolt 4 では、USB 4.0 をサポートし、最大転送レートの 40 Gbps 等は Thunderbolt 3 と同じです。

USB 3.1/3.2

USB 3.1 / USB 3.2 は、次のようになっています。USB 3.1 Gen1 では PCIe Gen2相当で、5 Gbps x 1、USB 3.1 Gen2 では PCIe Gen3相当で、10 Gbps x1 です。USB 3.2 では、x2モードがあり、それぞれ倍になり、10 Gbps、20 Gbps になります。x1モードの時のインターフェースは Type-A, Type-C, microUSB ですが、x2モードの時のインターフェースは Type-C のみです。Type-C になり、x2の信号を使えるようになったわけです。

  • USB 3.2 Gen1x1 (USB 3.1 Gen1 と同じ)
  • USB 3.2 Gen1x2
  • USB 3.2 Gen2x1 (USB 3.1 Gen2 と同じ)
  • USB 3.2 Gen2x2

Thunderboltは、もともと、PCI Express と DisplayPort のインターフェースを持っています。DisplayPort に関しては x4 での出力になるので、これを x2 の双方向で使えるのが USB 3.2 x2モードですね。

Apple M1の Thunderbolt 4

Apple Macbook Air の teardown のよると、

  • Intel JHL8040R Thunderbolt 4 Retimer (x2) (basically a Thunderbolt 4 extender/repeater)

が載っています。Thunderbolt は基本的には Intel のチップが必要になります。この チップは Apple M1 と接続しています。

Appleの Macbook Air のページには、下記のようにあります。

2つのThunderbolt / USB 4ポートで以下に対応:

  • 充電
  • DisplayPort
  • Thunderbolt 3(最大40Gb/s)
  • USB 4(最大40Gb/s)
  • USB 3.1 Gen 2(最大10Gb/s)
  • ヘッドフォン

USB 4 は、USB 3.2 Gen2x2 の 10 Gbps x2 = 20 Gbps の 2 倍の 40 Gbps になるということです。

Intel の Thunderbolt チップと Apple M1 の USB 3.1 Gen2 (Gen2なので 10Gbs) と DisplayPortをサポートしている部分と接続していることになります。

Apple M1機の Linuxの github を見ると、apple,dwc3-m1 とあります。dwc3 って、Synopsys の USB 3.x の IP のことです。

USB 3.1 (USB 3.1 Gen2x1は、USB 3.2 Gen2x1と同じ) と DisplayPort をサポートしている Synopsys の IP は、こちらですね。

DesignWare USB-C 3.1/DisplayPort 1.4 IP

この IP の特徴は、

  • Solution supports USB Type-C, SuperSpeed USB 3.1 at 10 Gbps, SuperSpeed USB 3.0 at 5 Gbps and High-Speed USB (USB 2.0) as well as DisplayPort 1.4 TX supporting RBR, HBR1, HBR2 and HBR3 bitrates

それから、Synopsys は、USB 3.2 の PHY も 持っているようです。

Apple M1 側は USB 3.1 Gen2 だけど、Intelの Thunderbolt 4 のチップを使うことで、インターフェース的には Thunderbolt 4 / USB 4 になっているんでしょうかね。

Intel JHL8540 から Intel JHL8040を妄想する

Apple M1機に載っていたのは、Intel JHL8040R Thunderbolt 4 Retimer ですが、IntelのThunderbolt 4 の発表の時に出てきたのは、Intel JHL8540 です。Intel JHL8540 については、下記の記事にブロック図(引用します)が載っていました。

www.anandtech.com

https://images.anandtech.com/doci/16333/jhl8540bd_575px.png

上記の図は、ここ (THunderboltのサイト) にオリジナルのPDFがありました。

PCIe x4 Gen3 と 2x DP 1.4 in (x4) を入力として、PCIe は内部に PCIe Switch が入っていて、USB 3.0 の xHCI Controller と Thunderbolt Switch に接続しているんですね。出力は、Thunderbolt 4 (DisplayPort 1.4a + USB 3.2 Gen2(USB 3.1 Gen2x1)が 2ポート。

ここから Intel JHL8040Rは、2x DP1.4a in (x4) と USB 3.1 Gen2 x 2 を入力として、上記の図の下の方の長い部分に直接つながっている感じですね。Apple M1機からは、USB 3.1 Gen2 と DisplayPort しか出ていなくて、Intel JHL8040R Retimer に繋げると、Thunderbolt 4 になるのが分かりました。これぐらいの機能であれば、チップとしてもそれほど高くなさそうです。
ここによると、Intel JHL8040は、2.4ドルのようです。

追記)
下記の記事に、Thunderbolt の Retimer の説明があった (Thunderbolt Retimerで検索したら、見つかった)

pc.watch.impress.co.jp

Retimer でも、PCIe Gen3 x4 と DisplayPort になっていますね。。あれ、ちょっと、混乱。。。もしかして、Apple M1 からは PCie と DisplayPort しか出ていないのかな?

JHL8040 のコードネームの Burnside Bridge でGoogle君に聞いたら、また出てきました

Burnside Bridge is a Type-C multi-protocol retimer to be used in on-board applications. Burnside Bridge offers the ability to latch protocol signals into on-chip memory before retransmitting them onwards. It can be used to extend the physical length of the system without increasing high frequency jitter.

Burnside Bridge supports spec compliant retimer of following protocols:
1. Display Port: four unidirectional DP lanes
2. USB3.1 Gen1/2: one bi-directional USB lane
3. Thunderbolt: two bi-directional CIO lanes
4. Multifunction Display (MFD): two unidirectional lanes of DP and one
bi-directional lane of USB3.1 Gen1/2

Note: Only item 1, 2 & 4 are supported in this CL. Item 3 support will
be added in follow on CLs.

ということで、やっぱり、DisplayPort と USB 3.1 Gen1/2 x1 (x2はサポートしていないっぽい)から Thunderbolt の x2 に変換しているっぽいですね。

おわりに

ちょっときになったので、Thunderbolt について調べてみました。

Thunderboltになると、USB 3.1 Gen2 と DisplayPort 1.4a が混在して流せるってどうなるのかな?と不思議に思いましたが、上記の Intel JHL8540のブロック図を見たら納得しました。
間に、USB 3.1 Gen2 と Display Port 1.4a を切り替えるための回路が入っているんですね。。。

個人的には、お仕事用とおしごと用に LenovoのノートPC と USB Type-C で接続できる Lenovo の 13インチのディスプレイを使っています。電源ケーブル無しにUSB Type-Cケーブルのみで接続できるのは本当に便利です。Thunderbolt になると、他にもいろいろと接続できるようになるということなので、Tiger LakeのノートPCを買えばいいんでしょうかね。。。

LenovoのノートPCでも、Tiger Lakeが載っているものが売っていますからね。

関連ブログ:
vengineer.hatenablog.com

Apple SoC Aシリーズ の USB

@Vengineerの戯言 : Twitter
SystemVerilogの世界へようこそすべては、SystemC v0.9公開から始まった 

はじめに

Apple M1では、USB は 3.2 / Thunderbolt 4 になっているのは、Apple M1機の USB 3.0/Thunderbolt 4にて確認しました。

では、iPhone はどうなっているのでしょうか?
iPhone とPCとの接続は、Linghting になっていますが、実態は USB 。実際にどうなっているかを、device tree を見て確認しました。

iPhone の USB

結果的には、

  • A10 から A12 までは、USB 2.0
  • A13 から A14 が、USB 3.0

でした。

A10からA12の device tree の USBの部分。OHCIEHCIなので、USB 2.0 っぽいです。OTGのPhyもありますからね。

      +--otgphyctrl:
      +--usb-complex:
         +--usb-device:
         +--usb-ehci0:
            +--usb-ehci0-port1:
         +--usb-ohci0:
            +--usb-ohci0-port1:

A13からA14の device tree の USBの部分。USB-DRD(Dual Role Device)なので、USB 3.0 っぽいです。

      +--atc-phy:
      +--usb-drd:
         +--usb-drd-port-hs:
      +--dart-usb:
         +--mapper-usb-device:
         +--mapper-usb-drd:
      +--atc-phy:
      +--usb-drd:
         +--usb-drd-port-hs:
      +--dart-usb:
         +--mapper-usb-device:
         +--mapper-usb-drd:

USB 3.0の方は、Apple M1機のLinuxにあったものと同じっぽいですね。ただし、Apple M1機は Thunderbolt 4をサポートしていますが。

おわりに

iPhone って、XS (A11)まで、USB 2.0 だったんですね。知らなかったです。。。

ここにも書いてありしたね。この記事では、iPhoneX までは USB 2.0となっていますね。

bokunonote.com

下記の記事では、iPad Pro 12.9(2017)、iPad Pro 10.5(2017)、iPad Pro 12.9(2015)は、USB 3.0 (Type-C)対応になっているんですね。給電用に使っている関係もあるんですかね。

socius101.com


関連ブログ:
vengineer.hatenablog.com
vengineer.hatenablog.com

Apple M1機のSSDについて

@Vengineerの戯言 : Twitter
SystemVerilogの世界へようこそすべては、SystemC v0.9公開から始まった 

はじめに

Apple M1機の内蔵SSDの性能については、下記の記事がありました。

iphone-mania.jp

Write(2190.1 MB/s), Read(2676.4 MB/s)

また、Apple M1機の外部SSDの性能について、下記の記事がありました。

www.itmedia.co.jp

対象機種は、以下の2機

テスト対象とした SSD は、以下の2点。

  • MIWAKURAのUSB 3.2 Gen 2接続M.2 NVMe SSD「MPC-DCM2U3C」
  • Samsung製Thunderbolt 3接続ポータブルSSDSamsung Portable SSD X5」

USB 3.2 Gen 2 接続ということは、10Gbps です。ということはざっくり最大 1.25 GB/s です。
Thunderbolt 3接続ということは、40Gbps です。ということはざっくり最大 5 GB/s です。

測定結果

  • M1 内蔵SSD : Write(2190.1 MB/s), Read(2676.4 MB/s)
  • M1 + USB 3.2 Gen2 : Write(546. 8MB/s), Read(523.2 MB/s)
  • Intel + USB 3.2 Gen2 : Write(891.1 MB/s), Read(948.9 MB/s)
  • M1 + Thunderbolt 3 : Write(2009.3 MB/s), Read(2573.1 MB/s)
  • Intel + Thnderbolt 3 : Write(1891.5 MB/s), Read(2576.3 MB/s)

M1のUSB 3.2 Gen2だと、Intelよりもかなり遅いですね。Macbook Pro (2019)だと第9世代Intel Core i7とすると、

(下図は、Intel,計25製品のデスクトップPC向け第9世代Coreプロセッサを発表。低消費電力版や100ドル未満の低価格CPUを拡充からの引用です)

Thunderbolt 3は、ICHから出ていますね。CPU側との接続が DMI 3.0 なので、実質 PCIe Gen3 x4 = 8Gbps x 4 = 32 Gbps = 4 GB/s が最大転送レートです。

この結果から、M1の内臓SSDは、外付けのThunderbolt 3 の転送性能 を超えていることがわかります。

f:id:Vengineer:20210222084704p:plain

Apple M1 でのSSDはどうなっているのか?

下記の記事によると、SSDの接続は、「Apple Fabric」なるあたらしいプロトコルというもので接続されているとあります。
ディスクユーティリティの接続の部分が、Apple Fabric になっている。一般的には、SSDは NVMe M.2 にて接続されています。M.2 は、信号的には PCIe Gen 3 x4 またPCIe Gen 4 x4 になっています。

news.mynavi.jp

2月1日のブログ、Apple M1 で SSD(NVMe)から Linux がブートできるようになったので、NVMe関連を眺めてみたにも書きましたが、Apple M機には SSDが2個載っています。仮に、2つのSSDが NVMe M.2 で接続されているとなると、PCIe x4 が2組必要になってきます。

ちなみに、Wikipediaには、下記のように、iPhone6S, 6S Plusでは、NVMe over PCIe を採用していて、その後の iPad ProとiPhone SEでも採用とありますので、NVMe over PCIe を使っていたことは確かなようです。

iPhone 6Sと6S Plusの発売でAppleスマートフォンにNVMe over PCIeを採用した初のモバイル展開を発表した。Appleはこれらの発売に続いて、同じくNVMe over PCIeを使用するiPad ProとiPhone SE を発売した。

iPhone の NVMe を探る

2月21日のブログ、Apple SoC AシリーズのPCIe ControlleriPhone 7(A10)から iPhone 12(A14)までの device tree を探りました。このブログでは、PCIe Controller についてみてみましたが、NVMe についても書きました。

A10では、pcie-bridge0 のところに、s3e というデバイスが付いていて、これが NVMe のようです。A11からは s3eではなく、ans(s4e)というデバイスになっています。この ansは PCIeに接続しているのではなく、別のデバイス(モジュール)になっています。つまり、iPhone 7 (A10)までは NVMe over PCIe を使っていたけど、iPhone X (A11)からは NVMe over PCIe ではなく、ansという NVMeモジュールを使っているということになります。

appleinsider.commashable.com


によると、下記のようにあるので変更はされたようですね。

There are also other specialized features of the A11 Bionic, including its super speedy SSD storage controller with custom ECC (error-correcting code) algorithms, as Johny Srouji, Apple's senior vice president of Hardware Technologies, detailed in an interview with Mashable.

Apple M1には、ans があるか?

Apple M1 で SSD(NVMe)から Linux がブートできるようになったので、NVMe関連を眺めてみた に戻ります。

dtsファイルに、ans なるモジュールがありますね。device_type は "pci"になっていますね。

あれ、となると、NVMe over PCIe なのか?

        ans: ans@27bcc0000 {
            compatible = "apple,nvme-m1";
            reg = <0x2 0x7bcc0000 0x0 0x40000  /* NVMe + Apple regs */
                   0x2 0x7bc50000 0x0 0x4000>; /* SART regs */
            interrupts = <0 590 4>;
            clocks = <&pcie_st_clk>;
            mboxes = <&ans_mbox 32>;

            #address-cells = <3>;
            #size-cells = <2>;
            #interrupt-cells = <1>;
            device_type = "pci";
            msi-controller;
            msi-parent = <&ans>;
            ranges = <0x02000000   0x0 0x7bc00000   0x2 0x7bc00000 0x0 0x00100000>;
            bus-range = <0x00 0x01>;
        };

そうです。Apple M1 で SSD(NVMe)から Linux がブートできるようになったので、NVMe関連を眺めてみたを書いたときは、NVMe over PCIe だとばっかり思っていました。ちょっと変だな?と思ったのは、pcie-apple-m1-nvme.c の PCIe Configuration Space へアクセス関数 (apple_m1_ans_config_read, apple_m1_ans_config_write)では、実デバイスにアクセスしているのではなく、デバイスドライバ内の構造体にあるメンバーにアクセスしているところです。

この commit のタイトルが、

Virtual PCI controller for Apple M1 NVMe storage.

というのも気になっていました。

dtsファイルには、ans_mbox というモジュールがあります。compatible に "apple,iop-mailbox-m1”とあります。

        ans_mbox: ans_mbox@277400000 {
            compatible = "apple,iop-mailbox-m1";
            reg = <0x2 0x77400000 0x0 0x20000>;
            interrupts = <0 583 4   0 586 4>;
            clocks = <&pcie_st_clk>;

            #mbox-cells = <1>;
            endpoints = <32>;
            wait-init;
        };

Documentによると、

Apple SoCs contain numerous IOPs (I/O Processors) that talk to the cores running Linux (AP) over a common mailbox protocol.

とあります。ということは、NVMe の制御にこのI/O Processorsを使っていることになるということです。

Thunderbolt 4

Apple M1機のSSDは、NVMe Over PCIe ではない違うインターフェースで接続されることまではわかりました。では、実際にどのようなインターフェースで接続しているのでしょうか?

Apple M1機には、Thunderbolt 4 が 2ポート搭載されています。Thunderbolt 4 は ここによると、PCデータ最低要件が

PCIe 32Gbps、USB 3.2 - 10Gbps

とあります。

SSDの測定結果の振り返ってみると、

  • M1 内蔵SSD : Write(2190.1 MB/s), Read(2676.4 MB/s)
  • M1 + Thunderbolt 3 : Write(2009.3 MB/s), Read(2573.1 MB/s)

となっていました。ということは、M1 内蔵SSDは、Thunderbolt3 ぐらいの性能は出るようになっている。Thunderbolt3 の PCデータ最低要件は、PCIe 16Gbps、USB 3.2 - 10Gbps です。Thunderbolt 4の半分です。
Apple M1機にはSSDが2個付いているので、Thunderbolt 3 相当が2個になり、これは Thunderbolt 4 相当になるということです。Wikipedia によると、Thunderbolt 3 および 4 では、PCIeのLane数が2となります。Thunderbolt 3 は 16 Gbps x2 = 32 Gbps = 4GB/s、Thunderbolt 4 は、32 Gbps x2 = 64 Gbps = 8 GB/s です。

ここからは妄想です。

Thunderbolt 4 を半分にしても4 GB/s になるの、SSDとは 1Laneで接続すればいいわけです。そうすることで、M1とSSDの接続が PCIe Gen4 x1 が 2組あればいいことになります。
SSD側は NVMe M.2 PCIe Gen4 x4 として開発しても、PCIe であれば x4 を x1 で接続しても問題無いはずです。

iPad Pro 用の A12X/A12Zを振り返ってみた

下の写真は、A12X搭載の iPad Pro 2018 の Teardown からの引用です。
1個しか実装されていませんが、SSDが2個載るようになっています。
https://d3nevzfk7ii3be.cloudfront.net/igi/SgrarV1BiJOVqPIl.huge

下の写真は、iPad Pro 12.9インチ(第4世代)の分解のビデオでも同様に、1個しか実装されていませんが、SSDが2個載るようになっています。

Apple M1機の開発用マシン(Developer Transition Kit)で使われた A12Z でも同じようにSSDが2個接続できるようになっていました。Developer Transition KitにはSSDが512GB搭載されているようですが、2個SSDが載っているかまでは確認とれていません。

おわりに

Apple M1機のSSDについていろいろと調べてみました。iPhone 7 (A10)までは、NVMe over PCIe を使っていたようですが、A11からは独自の方法(ansというもの)を使ってSSDと接続していたようです。M1でも同じ方法でSSDと接続していますが、iPhone用のAシリーズとはちょっと違って2つのSSDと接続しています。iPad 用の A12X/A12Zでも2つのSSDを接続していました。

Apple は A10 から A11 で大きな変更をしました。

  • CPU:pCoreとeCoreが同時に動くように変更
  • GPU を Imagination から 独自開発GPUに変更
  • L3 の削除
  • Neural Engine を投入、ただし、A12 Bionicに搭載されたものとは違うもの
  • SSDの接続をNVMe over PCIe から ans (なんの略語なんでしょうか?) に変更

最後の SSDの接続が今回の調査でわかったのは大きな収穫でした。

追記)、2021.02.26
IntelTiger Lake では、Thunderbolt 4 を ICHではなく、CPU側に置いている。


おまけに、Rocket Lakeでは、DMI 3.0 は x4 ではなく、x8 に。

もひとつおまけ、
Intel Thunderbolt 4 Update: Controllers and Tiger Lake in 2020 によると、
Data, PCIe at 32 Gbps (storage up to 3 GB/s)
とある、PCIe Gen4 だと、32 Gbpsなので 4GB/s になる。

関連ブログ:
vengineer.hatenablog.com
vengineer.hatenablog.com

Apple SoC Aシリーズの中がアクセラレータ

@Vengineerの戯言 : Twitter
SystemVerilogの世界へようこそすべては、SystemC v0.9公開から始まった 

はじめに

昨日の「Apple SoC AシリーズのPCIe Controller」の続き、Apple A10からA14 の device tree を眺めてみて、映像・画像処理に関するものをリストアップしました

アクセラレータ

A13およびA14ではアクセラレータが搭載されていました。

  • ave (ビデオ・エンコーダ)
  • avd (ビデオ・デコーダ)
  • disp0 (Video Processor ?)
  • isp (Image Signal Processor)
  • scaler (scaler and colourspace converter for the display)
  • rsm
  • JPEG x 2 channels

上記のすべてに、I/O MMUが付いています。また、avd、disp0、scalar に関しては、mapper が 2つ (+ mapper-XXX-piodma) 付いています。

A12 になかった アクセラレータ

  • rsm

A11 になかった アクセラレータ

  • ane (Apple Neural Engine)
  • A11で、GPUが Imagination から 独自開発に変わりました

A10

  • vxd

終わりに

A10の時点で、多くのアクセラレータが SoCの中に搭載されています。各 SoC では基本的には 1つか2つのアクセラレータが変わる感じだと思います。もちろん、既に搭載しているアクセラレータの強化は行っていると思います。

関連ブログ:
vengineer.hatenablog.com

Apple SoC AシリーズのPCIe Controller

@Vengineerの戯言 : Twitter
SystemVerilogの世界へようこそすべては、SystemC v0.9公開から始まった 

はじめに

2月15日のブログで、下記のように、「Apple M1機の PCI Express は、3ポート?」ということがわかりました。
vengineer.hatenablog.com

昨日、iPhone 7 (A10)から iPhone 12(A14)までの device tree を見つけたので、今日の午前中、各 device tree をにらめっこしていました。
このブログでは、にらめっこした結果、A10からA14までに PCIe Controller がどのようになっているのかもわかったのでまとめたいと思います。

A10からA14までの PCIe Controller

下記の device tree から PCIe Controller 部 (apcie) を調べました。

結果

  • ポート数:A10 - A13 は 4 port、A14 は 3 port
  • 接続デバイス(ポート番号):A10 (0 : s3e, 2 : wlan, 3: baseband)、A11 ( 2 : wlan, 3 : baseband), A12-A13 ( 2 : wlan/bt, 3 : baseband) 、A14 ( 1 : wlan/bt, 2 : baseband)

でした。A10-A13までは何で 4 port だったかはわかりませんが、A14 が 3 ポートになったのは、M1 と同じものを使ったんじゃないでしょうか?

A10 の s3e は何だろうと妄想しましたが、pci-bridge0 というところに、nvme という文字がありましたので、NVMe っぽいですね。

         +--pci-bridge0:
|  |  |  |  +--name 12 bytes: pci-bridge0
|  |  |  |  +--function-dart_force_active 8 bytes: (null)
|  |  |  |  +--msi-vector-base 4 bytes: (null)
|  |  |  |  +--nvme-mode 0 bytes: (null)
|  |  |  |  +--pci-l1pm-control 8 bytes: (null)
|  |  |  |  +--function-nvme_mmu_force_active 8 bytes: (null)
|  |  |  |  +--AAPL,unit-string 9 bytes: 00000000
|  |  |  |  +--t-refclk-to-perst 4 bytes: (null)
|  |  |  |  +--AAPL,phandle 4 bytes: (null)
|  |  |  |  +--clkreq-wait-time 4 bytes: (null)
|  |  |  |  +--#size-cells 4 bytes: (null)
|  |  |  |  +--function-clkreq 16 bytes: (null)
|  |  |  |  +--maximum-link-speed 4 bytes: (null)
|  |  |  |  +--#address-cells 4 bytes: (null)
|  |  |  |  +--function-perst 16 bytes: (null)
|  |  |  |  +--allow-endpoint-reset 0 bytes: (null)
|  |  |  |  +--apcie-port 4 bytes: (null)
|  |  |  |  +--#msi-vectors 4 bytes: (null)

s3e の中にも nvme とあるので、NVMe っぽいですね。A11 からは ans というモジュールか追加され、NVMeは専用回路になったようです。

            +--s3e:
|  |  |  |  |  +--pci-l1pm-control 8 bytes: (null)
|  |  |  |  |  +--#address-cells 4 bytes: (null)
|  |  |  |  |  +--AAPL,phandle 4 bytes: (null)
|  |  |  |  |  +--icc-20us-ma 4 bytes: (null)
|  |  |  |  |  +--AAPL,unit-string 9 bytes: 00000000
|  |  |  |  |  +--pci-phy-tx-eq 4 bytes: (null)
|  |  |  |  |  +--vcc-mv 4 bytes: (null)
|  |  |  |  |  +--icc-1us-ma 4 bytes: (null)
|  |  |  |  |  +--icc-5us-ma 4 bytes: (null)
|  |  |  |  |  +--pci-aspm-default 4 bytes: (null)
|  |  |  |  |  +--disable-pcie-phy-override 4 bytes: (null)
|  |  |  |  |  +--iommu-parent 12 bytes: (null)
|  |  |  |  |  +--function-pcie_port_control 12 bytes: (null)
|  |  |  |  |  +--name 4 bytes: s3e
|  |  |  |  |  +--function-boot_from_host 16 bytes: (null)
|  |  |  |  |  +--pci-phy-rx-eq 4 bytes: (null)
|  |  |  |  |  +--pci-max-latency 4 bytes: (null)
|  |  |  |  |  +--nvme-scratch-virt-region 16 bytes: (null)
|  |  |  |  |  +--icc-duration-idx 4 bytes: (null)
|  |  |  |  |  +--imp-mohm 4 bytes: (null)
|  |  |  |  |  +--indirection-size 4 bytes: (null)
|  |  |  |  |  +--device_type 12 bytes: (null)
|  |  |  |  |  +--#size-cells 4 bytes: (null)
|  |  |  |  |  +--write-perf-mlc-mbps 4 bytes: (null)
|  |  |  |  |  +--write-perf-tlc-mbps 4 bytes: (null)

iPhone 8 Plus (A11) では、下記のように、ans に s4e なるキーワードがあるので、NVMe 決定ですね。

      +--ans:
|  |  |  +--nvme-interrupt-idx 4 bytes: (null)
|  |  |  +--iop-version 4 bytes: (null)
|  |  |  +--clock-gates 4 bytes: (null)
|  |  |  +--function-spi0_sclk_config 16 bytes: (null)
|  |  |  +--AAPL,phandle 4 bytes: (null)
|  |  |  +--power-budget-platform-modes 64 bytes: (null)
|  |  |  +--iommu-parent 4 bytes: (null)
|  |  |  +--namespaces 84 bytes: (null)
|  |  |  +--name 4 bytes: ans
|  |  |  +--interrupt-parent 4 bytes: (null)
|  |  |  +--s4e-bfh-params 36 bytes: (null)

I/O MMU

I/O MMU ですが、PCIe の 各ポートにあるんですが、これ以外に、mapper なるものがありました。A12から wlan/bt の2つの機能が付いたデバイスが接続されていますが、各機能に対して Mapping しているようです。

f:id:Vengineer:20210221124045p:plain

iPhone 11 の場合は、次のようになっていました。

         +--mapper-apcie2-wlan:
|  |  |  |  +--compatible 13 bytes: iommu-mapper 0x69 0x6f 0x6d 0x6d 0x75 0x2d 0x6d 0x61 0x70 0x70 0x65 0x72 0x00
|  |  |  |  +--device_type 12 bytes: (null) 0x64 0x61 0x72 0x74 0x2d 0x6d 0x61 0x70 0x70 0x65 0x72 0x00
|  |  |  |  +--name 19 bytes: mapper-apcie2-wlan
|  |  |  |  +--AAPL,phandle 4 bytes: (null) 0xcd 0x00 0x00 0x00
         +--mapper-apcie2-bt:
|  |  |  |  +--compatible 13 bytes: iommu-mapper 0x69 0x6f 0x6d 0x6d 0x75 0x2d 0x6d 0x61 0x70 0x70 0x65 0x72 0x00
|  |  |  |  +--device_type 12 bytes: (null) 0x64 0x61 0x72 0x74 0x2d 0x6d 0x61 0x70 0x70 0x65 0x72 0x00
|  |  |  |  +--name 17 bytes: mapper-apcie2-bt
|  |  |  |  +--AAPL,phandle 4 bytes: (null) 0xce 0x00 0x00 0x00

PCIe Port の I/O MMU は、16KBではなく、4KB のようです。この部分は従来と同じなんですね。たぶん、PCIe Device の互換性を考えるとそうなるんでしょうね。

      +--dart-apcie2:
|  |  |  +--vm-size 4 bytes: (null) 0x00 0x00 0xe0 0x3f
|  |  |  +--manual-availability 4 bytes: (null) 0x01 0x00 0x00 0x00
|  |  |  +--compatible 11 bytes: dart,t8020 0x64 0x61 0x72 0x74 0x2c 0x74 0x38 0x30 0x32 0x30 0x00
|  |  |  +--page-size 4 bytes: (null) 0x00 0x10 0x00 0x00

終わりに

iPhone 7 (A10) から 12 (A14)の device tree があったので、こういうことも調べられました。

Apple が apcie という独自の PCIe Controller を開発したのは、Apple M1機の PCI Express は、3ポート? でも書きましたが、そのほかに今回分かったこととして、mapper です。1つの PCIe チップに複数の機能が実装されている場合にこれが活躍するようです。

参考ブログ:
vengineer.hatenablog.com

vengineer.hatenablog.com