| // SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later |
| /* Copyright 2013-2018 IBM Corp. */ |
| |
| #ifndef pr_fmt |
| #define pr_fmt(fmt) "STB: " fmt |
| #endif |
| |
| #include <skiboot.h> |
| #include <string.h> |
| #include <opal-api.h> |
| #include <chip.h> |
| #include <xscom.h> |
| #include <inttypes.h> |
| #include "secureboot.h" |
| #include "cvc.h" |
| #include "mbedtls/sha512.h" |
| |
| /* |
| * Assembly interfaces to call into the Container Verification Code. |
| * func_ptr: CVC base address + offset |
| */ |
| ROM_response __cvc_verify_v1(void *func_ptr, ROM_container_raw *container, |
| ROM_hw_params *params); |
| void __cvc_sha512_v1(void *func_ptr, const uint8_t *data, size_t len, |
| uint8_t *digest); |
| |
| struct container_verification_code { |
| uint64_t start_addr; |
| uint64_t end_addr; |
| struct list_head service_list; |
| }; |
| |
| static struct container_verification_code *cvc = NULL; |
| static bool softrom = false; |
| static void *secure_rom_mem = NULL; |
| |
| static struct dt_node *cvc_resv_mem = NULL; |
| static struct dt_node *cvc_node = NULL; |
| |
| struct cvc_service { |
| int id; |
| uint64_t addr; /* base_addr + offset */ |
| uint32_t version; |
| struct list_node link; |
| }; |
| |
| static struct { |
| enum cvc_service_id id; |
| const char *name; |
| } cvc_service_map[] = { |
| { CVC_SHA512_SERVICE, "sha512" }, |
| { CVC_VERIFY_SERVICE, "verify" }, |
| }; |
| |
| static struct cvc_service *cvc_find_service(enum cvc_service_id id) |
| { |
| struct cvc_service *service; |
| if (!cvc) |
| return NULL; |
| |
| list_for_each(&cvc->service_list, service, link) { |
| if (service->id == id) |
| return service; |
| } |
| return NULL; |
| } |
| |
| static const char *cvc_service_map_name(enum cvc_service_id id) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(cvc_service_map); i++) { |
| if (cvc_service_map[i].id == id) |
| return cvc_service_map[i].name; |
| } |
| return NULL; |
| } |
| |
| static void cvc_register(uint64_t start_addr, uint64_t end_addr) |
| { |
| if (cvc) |
| return; |
| |
| cvc = malloc(sizeof(struct container_verification_code)); |
| assert(cvc); |
| cvc->start_addr = start_addr; |
| cvc->end_addr = end_addr; |
| list_head_init(&cvc->service_list); |
| prlog(PR_INFO, "Found CVC @ %" PRIx64 "-%" PRIx64 "\n", |
| start_addr, end_addr); |
| } |
| |
| static void cvc_service_register(uint32_t id, uint32_t offset, uint32_t version) |
| { |
| struct cvc_service *service; |
| const char *name; |
| |
| if (!cvc) |
| return; |
| |
| /* Service already registered? */ |
| if (cvc_find_service(id)) |
| return; |
| |
| if (cvc->start_addr + offset > cvc->end_addr) { |
| prlog(PR_WARNING, "CVC service @ %x out of range, " |
| "id=%d\n", offset, id); |
| return; |
| } |
| |
| name = cvc_service_map_name(id); |
| if (!name) { |
| prlog(PR_ERR, "CVC service %d not supported\n", id); |
| return; |
| } |
| |
| service = malloc(sizeof(struct cvc_service)); |
| assert(service); |
| service->id = id; |
| service->version = version; |
| service->addr = cvc->start_addr + offset; |
| list_add_tail(&cvc->service_list, &service->link); |
| prlog(PR_INFO, "Found CVC-%s @ %" PRIx64 ", version=%d\n", |
| name, service->addr, service->version); |
| } |
| |
| static int cvc_reserved_mem_init(struct dt_node *parent) { |
| struct dt_node *node, *service; |
| struct dt_node *reserved_mem; |
| struct dt_node *exports; |
| uint32_t phandle; |
| uint64_t addr, size; |
| |
| reserved_mem = dt_find_by_path(dt_root, "/ibm,hostboot/reserved-memory"); |
| if (!reserved_mem) { |
| prlog(PR_ERR, "/ibm,hostboot/reserved-memory not found\n"); |
| return -1; |
| } |
| |
| /* |
| * The container verification code is stored in a hostboot reserved |
| * memory which is pointed by the property |
| * /ibm,secureboot/ibm,container-verification-code/memory-region |
| */ |
| dt_for_each_child(parent, node) { |
| if (dt_node_is_compatible(node, "ibm,container-verification-code")) { |
| phandle = dt_prop_get_u32(node, "memory-region"); |
| cvc_resv_mem = dt_find_by_phandle(reserved_mem, phandle); |
| cvc_node = node; |
| break; |
| } |
| } |
| if (!cvc_resv_mem) { |
| prlog(PR_ERR, "CVC not found in /ibm,hostboot/reserved-memory\n"); |
| return -1; |
| } |
| addr = dt_get_address(cvc_resv_mem, 0, &size); |
| cvc_register(addr, addr + size-1); |
| |
| exports = dt_find_by_path(dt_root, "/ibm,opal/firmware/exports"); |
| if (!exports) { |
| prerror("OCC: dt node /ibm,opal/firmware/exports not found\n"); |
| return false; |
| } |
| dt_add_property_u64s(exports, "cvc", addr, size - 1); |
| |
| /* |
| * Each child of the CVC node describes a CVC service |
| */ |
| dt_for_each_child(node, service) { |
| uint32_t version, offset; |
| |
| version = dt_prop_get_u32(service, "version"); |
| offset = dt_prop_get_u32(service, "reg"); |
| |
| if (dt_node_is_compatible(service, "ibm,cvc-sha512")) |
| cvc_service_register(CVC_SHA512_SERVICE, offset, version); |
| else if (dt_node_is_compatible(service, "ibm,cvc-verify")) |
| cvc_service_register(CVC_VERIFY_SERVICE, offset, version); |
| else |
| prlog(PR_DEBUG, "unknown %s\n", service->name); |
| } |
| |
| return 0; |
| } |
| |
| #define SECURE_ROM_MEMORY_SIZE (16 * 1024) |
| #define SECURE_ROM_XSCOM_ADDRESS 0x02020017 |
| |
| #define SECURE_ROM_SHA512_OFFSET 0x20 |
| #define SECURE_ROM_VERIFY_OFFSET 0x30 |
| |
| static int cvc_secure_rom_init(void) { |
| const uint32_t reg_addr = SECURE_ROM_XSCOM_ADDRESS; |
| struct dt_node *exports; |
| struct proc_chip *chip; |
| uint64_t reg_data; |
| |
| if (!secure_rom_mem) { |
| secure_rom_mem = malloc(SECURE_ROM_MEMORY_SIZE); |
| assert(secure_rom_mem); |
| } |
| /* |
| * The logic that contains the ROM within the processor is implemented |
| * in a way that it only responds to CI (cache inhibited) operations. |
| * Due to performance issues we copy the verification code from the |
| * secure ROM to RAM. We use memcpy_from_ci() to do that. |
| */ |
| chip = next_chip(NULL); |
| xscom_read(chip->id, reg_addr, ®_data); |
| memcpy_from_ci(secure_rom_mem, (void*) reg_data, |
| SECURE_ROM_MEMORY_SIZE); |
| cvc_register((uint64_t)secure_rom_mem, |
| (uint64_t)secure_rom_mem + SECURE_ROM_MEMORY_SIZE-1); |
| |
| exports = dt_find_by_path(dt_root, "/ibm,opal/firmware/exports"); |
| if (!exports) { |
| prerror("OCC: dt node /ibm,opal/firmware/exports not found\n"); |
| return false; |
| } |
| |
| dt_add_property_u64s(exports, "securerom", (uint64_t)secure_rom_mem, |
| SECURE_ROM_MEMORY_SIZE-1); |
| |
| cvc_service_register(CVC_SHA512_SERVICE, SECURE_ROM_SHA512_OFFSET, 1); |
| cvc_service_register(CVC_VERIFY_SERVICE, SECURE_ROM_VERIFY_OFFSET, 1); |
| return 0; |
| } |
| |
| void cvc_update_reserved_memory_phandle(void) { |
| struct dt_node *reserved_mem; |
| |
| if (!cvc_resv_mem || !cvc_node) |
| return; |
| |
| /* |
| * The linux documentation, reserved-memory.txt, says that memory-region |
| * is a phandle that pairs to a children of /reserved-memory |
| */ |
| reserved_mem = dt_find_by_path(dt_root, "/reserved-memory"); |
| if (!reserved_mem) { |
| prlog(PR_ERR, "/reserved-memory not found\n"); |
| return; |
| } |
| cvc_resv_mem = dt_find_by_name(reserved_mem, cvc_resv_mem->name); |
| if (cvc_resv_mem) { |
| dt_check_del_prop(cvc_node, "memory-region"); |
| dt_add_property_cells(cvc_node, "memory-region", cvc_resv_mem->phandle); |
| } else { |
| prlog(PR_WARNING, "CVC not found in /reserved-memory\n"); |
| return; |
| } |
| |
| cvc_resv_mem = NULL; |
| cvc_node = NULL; |
| } |
| |
| int cvc_init(void) |
| { |
| struct dt_node *node; |
| int version; |
| int rc = 0; |
| |
| if (cvc) |
| return 0; |
| |
| node = dt_find_by_path(dt_root, "/ibm,secureboot"); |
| if (!node) |
| return -1; |
| |
| if (!secureboot_is_compatible(node, &version, NULL)) { |
| /** |
| * @fwts-label CVCNotCompatible |
| * @fwts-advice Compatible CVC driver not found. Probably, |
| * hostboot/mambo/skiboot has updated the |
| * /ibm,secureboot/compatible without adding a driver that |
| * supports it. |
| */ |
| prlog(PR_ERR, "%s FAILED, /ibm,secureboot not compatible.\n", |
| __func__); |
| return -1; |
| } |
| |
| /* Only in P8 the CVC is stored in a secure ROM */ |
| if (version == IBM_SECUREBOOT_V1 && |
| proc_gen == proc_gen_p8) { |
| rc = cvc_secure_rom_init(); |
| } else if (version == IBM_SECUREBOOT_SOFTROM) { |
| softrom = true; |
| } else if (version == IBM_SECUREBOOT_V2) { |
| rc = cvc_reserved_mem_init(node); |
| } else { |
| prlog(PR_ERR, "%s FAILED. /ibm,secureboot not supported\n", |
| __func__); |
| return -1; |
| } |
| return rc; |
| } |
| |
| int call_cvc_sha512(const uint8_t *data, size_t data_len, uint8_t *digest, |
| size_t digest_size) |
| { |
| struct cvc_service *service; |
| |
| if (!data || !digest || digest_size < SHA512_DIGEST_LENGTH) |
| return OPAL_PARAMETER; |
| |
| if (data_len <= 0) |
| return OPAL_SUCCESS; |
| |
| memset(digest, 0, SHA512_DIGEST_LENGTH); |
| if (softrom) { |
| mbedtls_sha512_context ctx; |
| mbedtls_sha512_init(&ctx); |
| mbedtls_sha512_starts(&ctx, 0); // SHA512 = 0 |
| mbedtls_sha512_update(&ctx, data, data_len); |
| mbedtls_sha512_finish(&ctx, digest); |
| mbedtls_sha512_free(&ctx); |
| return OPAL_SUCCESS; |
| } |
| |
| service = cvc_find_service(CVC_SHA512_SERVICE); |
| |
| if (!service) |
| return OPAL_UNSUPPORTED; |
| |
| if (service->version == 1) { |
| unsigned long msr = mfmsr(); |
| __cvc_sha512_v1((void*) service->addr, data, data_len, digest); |
| assert(msr == mfmsr()); |
| } else { |
| return OPAL_UNSUPPORTED; |
| } |
| |
| return OPAL_SUCCESS; |
| } |
| |
| int call_cvc_verify(void *container, size_t len, const void *hw_key_hash, |
| size_t hw_key_hash_size, __be64 *log) |
| { |
| ROM_hw_params hw_params; |
| ROM_response rc; |
| struct cvc_service *service; |
| |
| if (!container || len < SECURE_BOOT_HEADERS_SIZE || |
| !hw_key_hash || hw_key_hash_size <= 0) |
| return OPAL_PARAMETER; |
| |
| if (softrom) |
| return OPAL_UNSUPPORTED; |
| |
| service = cvc_find_service(CVC_VERIFY_SERVICE); |
| |
| if (!service) |
| return OPAL_UNSUPPORTED; |
| |
| memset(&hw_params, 0, sizeof(ROM_hw_params)); |
| memcpy(&hw_params.hw_key_hash, hw_key_hash, hw_key_hash_size); |
| |
| if (service->version == 1) { |
| unsigned long msr = mfmsr(); |
| rc = __cvc_verify_v1((void*) service->addr, |
| (ROM_container_raw*) container, |
| &hw_params); |
| assert(msr == mfmsr()); |
| } else { |
| return OPAL_UNSUPPORTED; |
| } |
| |
| if (log) |
| *log = hw_params.log; |
| |
| if (rc != ROM_DONE) |
| return OPAL_PARTIAL; |
| |
| return OPAL_SUCCESS; |
| } |