/* * Apple A6 (S5L8950X) AES accelerator and its two-channel DMA front-end. * * This models the register protocol used by the dumped A6 SecureROM for * Image3 KBAG unwrap and DATA encryption/decryption. The built-in GID slot * is populated only from an explicit lab key file; the key is never exposed * through MMIO or logs. * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "qemu/osdep.h" #include "qapi/error.h" #include "crypto/aes.h" #include "hw/misc/s5l8950x-aes.h" #include "hw/core/cpu.h" #include "hw/core/sysbus.h" #include "hw/core/qdev-properties.h" #include "qemu/log.h" #include "qemu/module.h" #include "qemu/units.h" #include "system/address-spaces.h" #include "system/dma.h" #include "target/arm/cpu.h" #define A6_AES_DMA_MMIO_SIZE 0x3000 #define A6_AES_MMIO_SIZE 0x2000 #define A6_AES_DMA_CHANNELS 3 #define A6_DMA_CONTROL 0x000 #define A6_DMA_CONFIG 0x004 #define A6_DMA_TRANSFER_SIZE 0x00c #define A6_DMA_DESCRIPTOR 0x014 #define A6_DMA_CHANNEL_SIZE 0x1000 #define A6_AES_CAPABILITIES 0x0000 #define A6_AES_CONTROL 0x1000 #define A6_AES_IV_BASE 0x1010 #define A6_AES_IV_END 0x1020 #define A6_AES_KEY_BASE 0x1020 #define A6_AES_KEY_END 0x1040 #define A6_AES_CTL_ENCRYPT BIT(16) #define A6_AES_CTL_CBC BIT(17) #define A6_AES_CTL_KEY_BITS_MASK (3u << 18) #define A6_AES_CTL_EXPLICIT_KEY BIT(20) #define A6_AES_CTL_KEY_SELECT_MASK (3u << 21) #define A6_AES_KEY_SELECT_GID 1 #define A6_AES_MAX_TRANSFER (16 * MiB) #define A6_IBEC_LOAD_BASE 0xbff00000u #define A6_IBEC_MIN_SIZE (256 * KiB) #define A6_IBOOT_RESET_VECTOR 0xea00000eu typedef struct S5L8950XIBECHandoff { uint8_t *payload; uint32_t length; } S5L8950XIBECHandoff; struct S5L8950XAESState { SysBusDevice parent_obj; MemoryRegion dma_mmio; MemoryRegion aes_mmio; char *gid_key_path; bool force_debug_uarts; bool authenticated_ibec_handoff; bool ibec_handoff_queued; uint8_t gid_key[32]; bool gid_key_loaded; uint32_t dma_control[A6_AES_DMA_CHANNELS]; uint32_t dma_config[A6_AES_DMA_CHANNELS]; uint32_t dma_transfer_size[A6_AES_DMA_CHANNELS]; uint32_t dma_descriptor[A6_AES_DMA_CHANNELS]; bool dma_started[A6_AES_DMA_CHANNELS]; uint32_t capabilities; uint32_t control; uint8_t iv[16]; uint8_t explicit_key[32]; }; static void s5l8950x_aes_handoff_ibec_on_cpu(CPUState *cpu, run_on_cpu_data data) { S5L8950XIBECHandoff *handoff = data.host_ptr; if (address_space_write_rom(&address_space_memory, A6_IBEC_LOAD_BASE, MEMTXATTRS_UNSPECIFIED, handoff->payload, handoff->length) != MEMTX_OK) { qemu_log_mask(LOG_GUEST_ERROR, "s5l8950x.aes: iBEC handoff write failed at 0x%08x\n", A6_IBEC_LOAD_BASE); goto out; } /* * iBSS has already completed the IMG3, ticket, nonce and AES work before * this callback is queued. The remaining physical operation is a warm * CPU transfer into the linked iBEC address; resetting the whole machine * would incorrectly re-enter SecureROM and consume another core-entry * slot in its retained SRAM state. */ cpu_reset(cpu); ARM_CPU(cpu)->env.regs[0] = 1; cpu->halted = 0; cpu_set_pc(cpu, A6_IBEC_LOAD_BASE); cpu_exit(cpu); qemu_log_mask(LOG_UNIMP, "s5l8950x.aes: transferred authenticated iBEC (%u bytes) " "to PC=0x%08x\n", handoff->length, A6_IBEC_LOAD_BASE); out: g_free(handoff->payload); g_free(handoff); } static void s5l8950x_aes_queue_ibec_handoff(S5L8950XAESState *s, const uint8_t *payload, uint32_t length) { S5L8950XIBECHandoff *handoff; CPUState *cpu = first_cpu; if (!cpu || s->ibec_handoff_queued || length < A6_IBEC_MIN_SIZE || ldl_le_p(payload) != A6_IBOOT_RESET_VECTOR) { return; } handoff = g_new(S5L8950XIBECHandoff, 1); handoff->payload = g_memdup2(payload, length); handoff->length = length; s->ibec_handoff_queued = true; async_run_on_cpu(cpu, s5l8950x_aes_handoff_ibec_on_cpu, RUN_ON_CPU_HOST_PTR(handoff)); } static bool s5l8950x_aes_enable_bootloader_uarts(uint8_t *image, size_t image_length, hwaddr load_base) { static const uint16_t helper_tail[] = { 0x680a, /* ldr r2, [r1] */ 0x4310, /* orrs r0, r2 */ 0x6008, /* str r0, [r1] */ 0x4770, /* bx lr */ }; static const uint8_t movs_r0_3[] = { 0x03, 0x20 }; for (size_t offset = 0; offset + 10 <= image_length; offset += 2) { uint16_t ldr_literal = lduw_le_p(image + offset); size_t i; if ((ldr_literal & 0xff00) != 0x4900) { continue; } for (i = 0; i < ARRAY_SIZE(helper_tail); i++) { if (lduw_le_p(image + offset + 2 + i * 2) != helper_tail[i]) { break; } } if (i != ARRAY_SIZE(helper_tail)) { continue; } memcpy(image + offset + 4, movs_r0_3, sizeof(movs_r0_3)); qemu_log_mask(LOG_UNIMP, "s5l8950x.uart: forced debug-uarts=3 after native " "IMG3 decrypt at 0x%08" HWADDR_PRIx "\n", load_base + offset + 4); return true; } return false; } static bool s5l8950x_aes_read_descriptor(uint32_t address, hwaddr *buffer, uint32_t *length) { uint8_t descriptor[16]; if (!address || dma_memory_read(&address_space_memory, address, descriptor, sizeof(descriptor), MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { return false; } /* * A6 DMA addresses are 32-bit physical addresses. ldl_le_p() returns a * signed target-endian value on this host, so widening it directly to * hwaddr sign-extends SRAM addresses such as 0xbfe00b00. Preserve the * descriptor's raw 32-bit bit pattern before widening it. */ *buffer = (uint32_t)ldl_le_p(descriptor + 8); *length = ldl_le_p(descriptor + 12); return true; } static size_t s5l8950x_aes_key_length(uint32_t control) { switch ((control & A6_AES_CTL_KEY_BITS_MASK) >> 18) { case 0: return 16; case 1: return 24; case 2: return 32; default: return 0; } } static bool s5l8950x_aes_crypt(const uint8_t *source, uint8_t *destination, size_t length, const uint8_t *key, size_t key_length, const uint8_t iv[16], bool cbc, bool encrypt) { AES_KEY expanded_key; uint8_t chain[AES_BLOCK_SIZE]; uint8_t block[AES_BLOCK_SIZE]; if ((encrypt ? AES_set_encrypt_key(key, key_length * 8, &expanded_key) : AES_set_decrypt_key(key, key_length * 8, &expanded_key))) { return false; } memcpy(chain, iv, sizeof(chain)); for (size_t offset = 0; offset < length; offset += AES_BLOCK_SIZE) { if (encrypt) { for (size_t i = 0; i < AES_BLOCK_SIZE; i++) { block[i] = source[offset + i] ^ (cbc ? chain[i] : 0); } AES_encrypt(block, destination + offset, &expanded_key); if (cbc) { memcpy(chain, destination + offset, sizeof(chain)); } } else { AES_decrypt(source + offset, block, &expanded_key); for (size_t i = 0; i < AES_BLOCK_SIZE; i++) { destination[offset + i] = block[i] ^ (cbc ? chain[i] : 0); } if (cbc) { memcpy(chain, source + offset, sizeof(chain)); } } } return true; } static bool s5l8950x_aes_process(S5L8950XAESState *s) { g_autoptr(GByteArray) source = NULL; g_autoptr(GByteArray) destination = NULL; const uint8_t *key; const char *key_name; hwaddr source_address; hwaddr destination_address; uint32_t source_length; uint32_t destination_length; uint32_t key_select; size_t key_length; bool cbc; bool encrypt; if (!s5l8950x_aes_read_descriptor(s->dma_descriptor[1], &source_address, &source_length) || !s5l8950x_aes_read_descriptor(s->dma_descriptor[2], &destination_address, &destination_length)) { qemu_log_mask(LOG_GUEST_ERROR, "s5l8950x.aes: invalid DMA descriptor\n"); return false; } if (!source_length || source_length != destination_length || source_length > A6_AES_MAX_TRANSFER || source_length % 16) { qemu_log_mask(LOG_GUEST_ERROR, "s5l8950x.aes: invalid DMA lengths %u/%u\n", source_length, destination_length); return false; } key_length = s5l8950x_aes_key_length(s->control); if (!key_length) { qemu_log_mask(LOG_GUEST_ERROR, "s5l8950x.aes: unsupported key-size selector\n"); return false; } key_select = (s->control & A6_AES_CTL_KEY_SELECT_MASK) >> 21; if (key_select == A6_AES_KEY_SELECT_GID) { if (!s->gid_key_loaded || key_length != sizeof(s->gid_key)) { qemu_log_mask(LOG_GUEST_ERROR, "s5l8950x.aes: GID operation requested without " "a matching 256-bit lab key\n"); return false; } key = s->gid_key; key_name = "lab-GID"; } else if (key_select == 0 && (s->control & A6_AES_CTL_EXPLICIT_KEY)) { key = s->explicit_key; key_name = "explicit"; } else { qemu_log_mask(LOG_GUEST_ERROR, "s5l8950x.aes: unsupported built-in key slot %u\n", key_select); return false; } cbc = (s->control & A6_AES_CTL_CBC) != 0; encrypt = (s->control & A6_AES_CTL_ENCRYPT) != 0; source = g_byte_array_sized_new(source_length); g_byte_array_set_size(source, source_length); destination = g_byte_array_sized_new(destination_length); g_byte_array_set_size(destination, destination_length); if (dma_memory_read(&address_space_memory, source_address, source->data, source_length, MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { qemu_log_mask(LOG_GUEST_ERROR, "s5l8950x.aes: DMA source read failed at 0x%08" HWADDR_PRIx "\n", source_address); return false; } if (!s5l8950x_aes_crypt(source->data, destination->data, source_length, key, key_length, s->iv, cbc, encrypt)) { qemu_log_mask(LOG_GUEST_ERROR, "s5l8950x.aes: AES operation failed\n"); return false; } if (s->force_debug_uarts && !encrypt && key_select == 0 && (s->control & A6_AES_CTL_EXPLICIT_KEY) && !s5l8950x_aes_enable_bootloader_uarts(destination->data, destination_length, destination_address)) { qemu_log_mask(LOG_GUEST_ERROR, "s5l8950x.uart: debug_enable_uarts helper not found " "after native IMG3 decrypt\n"); } if (dma_memory_write(&address_space_memory, destination_address, destination->data, destination_length, MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) { qemu_log_mask(LOG_GUEST_ERROR, "s5l8950x.aes: DMA destination write failed at 0x%08" HWADDR_PRIx "\n", destination_address); return false; } if (s->authenticated_ibec_handoff && !encrypt && key_select == 0 && (s->control & A6_AES_CTL_EXPLICIT_KEY)) { s5l8950x_aes_queue_ibec_handoff(s, destination->data, destination_length); } qemu_log_mask(LOG_UNIMP, "s5l8950x.aes: %s AES-%zu-%s key=%s bytes=%u " "src=0x%08" HWADDR_PRIx " dst=0x%08" HWADDR_PRIx "\n", encrypt ? "encrypt" : "decrypt", key_length * 8, cbc ? "CBC" : "ECB", key_name, source_length, source_address, destination_address); return true; } static uint64_t s5l8950x_aes_dma_read(void *opaque, hwaddr offset, unsigned size) { S5L8950XAESState *s = opaque; unsigned channel = offset / A6_DMA_CHANNEL_SIZE; hwaddr reg = offset % A6_DMA_CHANNEL_SIZE; if (channel >= A6_AES_DMA_CHANNELS) { return 0; } switch (reg) { case A6_DMA_CONTROL: return s->dma_control[channel]; case A6_DMA_CONFIG: return s->dma_config[channel]; case A6_DMA_TRANSFER_SIZE: return s->dma_transfer_size[channel]; case A6_DMA_DESCRIPTOR: return s->dma_descriptor[channel]; default: return 0; } } static void s5l8950x_aes_dma_write(void *opaque, hwaddr offset, uint64_t value, unsigned size) { S5L8950XAESState *s = opaque; unsigned channel = offset / A6_DMA_CHANNEL_SIZE; hwaddr reg = offset % A6_DMA_CHANNEL_SIZE; if (channel >= A6_AES_DMA_CHANNELS) { return; } switch (reg) { case A6_DMA_CONTROL: s->dma_control[channel] = value; qemu_log_mask(LOG_UNIMP, "s5l8950x.aes: DMA%u control=0x%08" PRIx64 "\n", channel, value); if (value == 2) { s->dma_started[channel] = false; } else if (channel && (value & 1)) { s->dma_started[channel] = true; if (s->dma_started[1] && s->dma_started[2]) { s5l8950x_aes_process(s); s->dma_started[1] = false; s->dma_started[2] = false; /* Status 0 is idle/complete; SecureROM polls bits 16..17. */ s->dma_control[1] &= ~(3u << 16); s->dma_control[2] &= ~(3u << 16); } } break; case A6_DMA_CONFIG: s->dma_config[channel] = value; qemu_log_mask(LOG_UNIMP, "s5l8950x.aes: DMA%u config=0x%08" PRIx64 "\n", channel, value); break; case A6_DMA_TRANSFER_SIZE: s->dma_transfer_size[channel] = value; qemu_log_mask(LOG_UNIMP, "s5l8950x.aes: DMA%u length=0x%08" PRIx64 "\n", channel, value); break; case A6_DMA_DESCRIPTOR: s->dma_descriptor[channel] = value; qemu_log_mask(LOG_UNIMP, "s5l8950x.aes: DMA%u descriptor=0x%08" PRIx64 "\n", channel, value); break; default: break; } } static uint64_t s5l8950x_aes_read(void *opaque, hwaddr offset, unsigned size) { S5L8950XAESState *s = opaque; switch (offset) { case A6_AES_CAPABILITIES: return s->capabilities; case A6_AES_CONTROL: return s->control; case A6_AES_IV_BASE ... A6_AES_IV_END - 4: return ldl_le_p(s->iv + offset - A6_AES_IV_BASE); case A6_AES_KEY_BASE ... A6_AES_KEY_END - 4: /* Explicit key registers are write-only on the real device. */ return 0; default: return 0; } } static void s5l8950x_aes_write(void *opaque, hwaddr offset, uint64_t value, unsigned size) { S5L8950XAESState *s = opaque; switch (offset) { case A6_AES_CAPABILITIES: s->capabilities = value; break; case A6_AES_CONTROL: s->control = value; qemu_log_mask(LOG_UNIMP, "s5l8950x.aes: control=0x%08" PRIx64 "\n", value); break; case A6_AES_IV_BASE ... A6_AES_IV_END - 4: stl_le_p(s->iv + offset - A6_AES_IV_BASE, value); break; case A6_AES_KEY_BASE ... A6_AES_KEY_END - 4: stl_le_p(s->explicit_key + offset - A6_AES_KEY_BASE, value); break; default: break; } } static const MemoryRegionOps s5l8950x_aes_dma_ops = { .read = s5l8950x_aes_dma_read, .write = s5l8950x_aes_dma_write, .endianness = DEVICE_LITTLE_ENDIAN, .valid.min_access_size = 4, .valid.max_access_size = 4, }; static const MemoryRegionOps s5l8950x_aes_ops = { .read = s5l8950x_aes_read, .write = s5l8950x_aes_write, .endianness = DEVICE_LITTLE_ENDIAN, .valid.min_access_size = 4, .valid.max_access_size = 4, }; static void s5l8950x_aes_reset(DeviceState *dev) { S5L8950XAESState *s = S5L8950X_AES(dev); memset(s->dma_control, 0, sizeof(s->dma_control)); memset(s->dma_config, 0, sizeof(s->dma_config)); memset(s->dma_transfer_size, 0, sizeof(s->dma_transfer_size)); memset(s->dma_descriptor, 0, sizeof(s->dma_descriptor)); memset(s->dma_started, 0, sizeof(s->dma_started)); s->capabilities = 0; s->control = 0; memset(s->iv, 0, sizeof(s->iv)); memset(s->explicit_key, 0, sizeof(s->explicit_key)); s->ibec_handoff_queued = false; } static void s5l8950x_aes_realize(DeviceState *dev, Error **errp) { S5L8950XAESState *s = S5L8950X_AES(dev); g_autofree gchar *contents = NULL; gsize length = 0; g_autoptr(GError) error = NULL; if (!s->gid_key_path) { return; } if (!g_file_get_contents(s->gid_key_path, &contents, &length, &error)) { error_setg(errp, "could not load A6 lab GID key '%s': %s", s->gid_key_path, error->message); return; } if (length != sizeof(s->gid_key)) { error_setg(errp, "A6 lab GID key '%s' must be exactly 32 bytes", s->gid_key_path); return; } memcpy(s->gid_key, contents, sizeof(s->gid_key)); s->gid_key_loaded = true; qemu_log_mask(LOG_UNIMP, "s5l8950x.aes: loaded private 256-bit lab GID key\n"); } static void s5l8950x_aes_init(Object *object) { S5L8950XAESState *s = S5L8950X_AES(object); SysBusDevice *sbd = SYS_BUS_DEVICE(object); memory_region_init_io(&s->dma_mmio, object, &s5l8950x_aes_dma_ops, s, TYPE_S5L8950X_AES ".dma", A6_AES_DMA_MMIO_SIZE); sysbus_init_mmio(sbd, &s->dma_mmio); memory_region_init_io(&s->aes_mmio, object, &s5l8950x_aes_ops, s, TYPE_S5L8950X_AES, A6_AES_MMIO_SIZE); sysbus_init_mmio(sbd, &s->aes_mmio); } static const Property s5l8950x_aes_properties[] = { DEFINE_PROP_STRING("gid-key-file", S5L8950XAESState, gid_key_path), DEFINE_PROP_BOOL("force-debug-uarts", S5L8950XAESState, force_debug_uarts, false), DEFINE_PROP_BOOL("authenticated-ibec-handoff", S5L8950XAESState, authenticated_ibec_handoff, false), }; static void s5l8950x_aes_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); dc->realize = s5l8950x_aes_realize; device_class_set_legacy_reset(dc, s5l8950x_aes_reset); device_class_set_props(dc, s5l8950x_aes_properties); } static const TypeInfo s5l8950x_aes_type_info = { .name = TYPE_S5L8950X_AES, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(S5L8950XAESState), .instance_init = s5l8950x_aes_init, .class_init = s5l8950x_aes_class_init, }; static void s5l8950x_aes_register_types(void) { type_register_static(&s5l8950x_aes_type_info); } type_init(s5l8950x_aes_register_types)