| // SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later |
| // Copyright 2022 IBM Corp. |
| |
| #define pr_fmt(fmt) "PLDM: " fmt |
| |
| #include <endian.h> |
| #include <lock.h> |
| #include <opal-api.h> |
| #include <libflash/errors.h> |
| #include <libflash/ffs.h> |
| #include "pldm.h" |
| |
| /* |
| * This struct is used to map a PNOR sections. |
| * The content is deriving from the hb_lid_ids PLDM BIOS Attribute. |
| */ |
| struct pldm_lid { |
| struct list_node list; |
| uint32_t start; |
| uint32_t handle; |
| uint32_t length; |
| char name[FFS_PART_NAME_MAX + 1]; |
| char id[FFS_PART_NAME_MAX + 1]; |
| }; |
| |
| static LIST_HEAD(lid_files); |
| |
| struct pldm_ctx_data { |
| /* Members protected by the blocklevel lock */ |
| struct blocklevel_device bl; |
| uint32_t total_size; |
| uint32_t erase_granule; |
| struct lock lock; |
| }; |
| |
| #define MEGABYTE (1024*1024) |
| |
| /* |
| * When using PLDM for PNOR Resource Provider operations, |
| * reserve 32 MB of VMM address space per section. |
| * Note that all of this space may not actually be used by each section. |
| */ |
| #define VMM_SIZE_RESERVED_PER_SECTION (32 * MEGABYTE) |
| |
| #define ERASE_GRANULE_DEF 0x1000 |
| |
| /* 'fake' header flash */ |
| struct __ffs_hdr *raw_hdr; |
| size_t raw_hdr_size; |
| |
| /* |
| * Print the attributes of lid files. |
| */ |
| static void print_lid_files_attr(void) |
| { |
| struct pldm_lid *lid = NULL; |
| |
| list_for_each(&lid_files, lid, list) |
| prlog(PR_NOTICE, "name: %s, id: %s, handle: %d, length: 0x%x, start: 0x%x\n", |
| lid->name, lid->id, lid->handle, lid->length, lid->start); |
| } |
| |
| /* |
| * Return the number of lid files. |
| */ |
| static uint32_t get_lids_count(void) |
| { |
| struct pldm_lid *lid = NULL; |
| uint32_t count = 0; |
| |
| list_for_each(&lid_files, lid, list) |
| count++; |
| |
| return count; |
| } |
| |
| /* |
| * parse the "hb_lid_ids" string |
| * <ATTR_a>=<lid_id_1>,<ATTR_b>=<lid_id_2> |
| */ |
| static int parse_hb_lid_ids_string(char *str) |
| { |
| struct pldm_lid *lid, *tmp; |
| const char *pp = "="; |
| char *attr, *attr_end; |
| int rc, count = 1; |
| char *lid_id; |
| |
| for (char *p = strtok(str, ","); p != NULL; p = strtok(NULL, ",")) { |
| lid = zalloc(sizeof(struct pldm_lid)); |
| if (!lid) { |
| prlog(PR_ERR, "Error allocating pldm_lid structure\n"); |
| rc = OPAL_NO_MEM; |
| goto err; |
| } |
| |
| /* parse the string <attr>=<lid_id> */ |
| attr = p; |
| while ((*pp != *p) && (*p != '\0')) |
| p++; |
| |
| attr_end = p; |
| lid_id = ++p; |
| *attr_end = '\0'; |
| |
| strcpy(lid->name, attr); |
| strcpy(lid->id, lid_id); |
| |
| /* reserve 32 MB of VMM address space per section. |
| * Address 0x0 -> 0x2000000: 'fake' header flash |
| * Address 0x2000000 -> 0x4000000: lid id 1 |
| * Address 0x4000000 -> 0x6000000: lid id 2 |
| * .... |
| */ |
| lid->start = VMM_SIZE_RESERVED_PER_SECTION * count; |
| |
| /* handle and length */ |
| rc = pldm_find_file_handle_by_lid_id(lid->id, |
| &lid->handle, |
| &lid->length); |
| /* OPAL_PARAMETER means that lid_id is present in hb_lid_ids, |
| * but we don't have any file attribute information in the |
| * file table, so continue on the next item. |
| */ |
| if ((rc) && (rc != OPAL_PARAMETER)) |
| goto err; |
| |
| if (lid->length > VMM_SIZE_RESERVED_PER_SECTION) { |
| prlog(PR_ERR, "file length (0x%x) > virtual size reserved per " |
| "section (0x%x)\n", |
| lid->length, VMM_SIZE_RESERVED_PER_SECTION); |
| rc = OPAL_RESOURCE; |
| goto err; |
| } |
| |
| /* add new member in the global list */ |
| list_add_tail(&lid_files, &lid->list); |
| |
| count++; |
| } |
| |
| return OPAL_SUCCESS; |
| |
| err: |
| /* free all lid entries */ |
| list_for_each_safe(&lid_files, lid, tmp, list) |
| free(lid); |
| |
| return rc; |
| } |
| |
| /* |
| * Parse the "hb_lid_ids" string from bios tables and complete |
| * the global list of lid files. |
| */ |
| static int lid_ids_to_vaddr_mapping(void) |
| { |
| char *lid_ids_string = NULL; |
| int rc; |
| |
| /* get lid ids string from bios tables */ |
| rc = pldm_bios_get_lids_id(&lid_ids_string); |
| if (rc) |
| goto out; |
| |
| /* parse the "hb_lid_ids" string */ |
| rc = parse_hb_lid_ids_string(lid_ids_string); |
| |
| out: |
| if (lid_ids_string) |
| free(lid_ids_string); |
| |
| return rc; |
| } |
| |
| static uint32_t checksum(void *data, size_t size) |
| { |
| uint32_t i, csum = 0; |
| |
| for (i = csum = 0; i < (size/4); i++) |
| csum ^= ((uint32_t *)data)[i]; |
| return csum; |
| } |
| |
| /* Helper functions for typesafety and size safety */ |
| static uint32_t hdr_checksum(struct __ffs_hdr *hdr) |
| { |
| return checksum(hdr, sizeof(struct __ffs_hdr)); |
| } |
| |
| static uint32_t entry_checksum(struct __ffs_entry *ent) |
| { |
| return checksum(ent, sizeof(struct __ffs_entry)); |
| } |
| |
| /* |
| * Fill __ffs structures in order to return a 'fake' header flash |
| */ |
| static int lid_ids_to_header_flash(void *buf, uint64_t len) |
| { |
| struct __ffs_entry *entry; |
| struct pldm_lid *lid = NULL; |
| uint32_t count, part_id, i; |
| uint32_t block_size; |
| |
| /* reading the flash header has already been requested */ |
| if (raw_hdr) { |
| (raw_hdr_size < len) ? memcpy(buf, raw_hdr, raw_hdr_size) : |
| memcpy(buf, raw_hdr, len); |
| return OPAL_SUCCESS; |
| } |
| |
| /* number of lid files */ |
| count = get_lids_count(); |
| |
| /* last member of struct __ffs_hdr is a flexible array member */ |
| raw_hdr_size = sizeof(struct __ffs_hdr) + (count * sizeof(struct __ffs_entry)); |
| raw_hdr = zalloc(raw_hdr_size); |
| if (!raw_hdr) |
| return OPAL_NO_MEM; |
| |
| /* complete header flash |
| * Represents the on flash layout of FFS structures |
| * Note: Beware that the size of the partition table is in units of block_size |
| * |
| * @magic: Eye catcher/corruption detector |
| * @version: Version of the structure |
| * @size: Size of partition table (in block_size) |
| * @entry_size: Size of struct __ffs_entry element (in bytes) |
| * @entry_count: Number of struct __ffs_entry elements in @entries array |
| * @block_size: Size of block on device (in bytes) |
| * @block_count: Number of blocks on device |
| * @checksum: Header checksum |
| */ |
| /* size of the cached map: block_size * raw_hdr->size |
| * raw_hdr->size = 0x3: we take a little margin if the number |
| * of element would increase |
| */ |
| block_size = ERASE_GRANULE_DEF; |
| |
| raw_hdr->magic = cpu_to_be32(FFS_MAGIC); |
| raw_hdr->version = cpu_to_be32(FFS_VERSION_1); |
| raw_hdr->size = cpu_to_be32(0x3); |
| raw_hdr->entry_size = cpu_to_be32(sizeof(struct __ffs_entry)); |
| raw_hdr->entry_count = cpu_to_be32(count); |
| raw_hdr->block_size = cpu_to_be32(block_size); |
| raw_hdr->block_count = cpu_to_be32(0x4000); /* value from IPMI/PNOR protocol */ |
| raw_hdr->checksum = hdr_checksum(raw_hdr); |
| |
| lid = list_top(&lid_files, struct pldm_lid, list); |
| part_id = 1; |
| |
| for (i = 0; i < count; i++) { |
| entry = &raw_hdr->entries[i]; |
| |
| memcpy(entry->name, lid->name, sizeof(entry->name)); |
| entry->name[FFS_PART_NAME_MAX] = '\0'; |
| entry->base = cpu_to_be32(lid->start / block_size); |
| entry->size = cpu_to_be32(lid->length / block_size); |
| entry->pid = cpu_to_be32(FFS_PID_TOPLEVEL); |
| entry->id = cpu_to_be32(part_id); |
| entry->type = cpu_to_be32(0x1); |
| entry->flags = cpu_to_be32(0x0); |
| entry->actual = cpu_to_be32(lid->length); |
| entry->checksum = entry_checksum(entry); |
| |
| lid = list_next(&lid_files, lid, list); |
| part_id++; |
| } |
| |
| /* fill in rquester buffer */ |
| (raw_hdr_size < len) ? memcpy(buf, raw_hdr, raw_hdr_size) : |
| memcpy(buf, raw_hdr, len); |
| |
| return OPAL_SUCCESS; |
| } |
| |
| /* |
| * Search lid member from the virtual address. |
| */ |
| static int vaddr_to_lid_id(uint64_t pos, uint32_t *start, uint32_t *handle, |
| uint32_t *length) |
| { |
| struct pldm_lid *lid = NULL; |
| |
| list_for_each(&lid_files, lid, list) { |
| if ((pos >= lid->start) && (pos < lid->start + VMM_SIZE_RESERVED_PER_SECTION)) { |
| *start = lid->start; |
| *handle = lid->handle; |
| *length = lid->length; |
| return OPAL_SUCCESS; |
| } |
| } |
| |
| return OPAL_PARAMETER; |
| } |
| |
| static int lid_files_read(struct blocklevel_device *bl __unused, |
| uint64_t pos, void *buf, uint64_t len) |
| { |
| uint32_t lid_start, lid_handle, lid_length; |
| int rc = OPAL_SUCCESS; |
| uint64_t offset; |
| |
| /* LPC is only 32bit */ |
| if (pos > UINT_MAX || (pos + len) > UINT_MAX) |
| return FLASH_ERR_PARM_ERROR; |
| |
| prlog(PR_TRACE, "lid files read at 0x%llx for 0x%llx\n", |
| pos, len); |
| |
| if ((pos == 0) || (pos <= (ERASE_GRANULE_DEF * 0x3))) { |
| /* return a 'fake' header flash or cached map */ |
| rc = lid_ids_to_header_flash(buf, len); |
| } else { |
| /* convert offset to lid id */ |
| rc = vaddr_to_lid_id(pos, &lid_start, |
| &lid_handle, &lid_length); |
| if (rc) |
| return rc; |
| |
| /* read lid file */ |
| offset = pos - lid_start; |
| rc = pldm_file_io_read_file(lid_handle, lid_length, |
| offset, buf, len); |
| } |
| |
| return rc; |
| } |
| |
| static int lid_files_write(struct blocklevel_device *bl __unused, |
| uint64_t pos, const void *buf __unused, |
| uint64_t len) |
| { |
| prlog(PR_ERR, "lid files writes at 0x%llx for 0x%llx\n", |
| pos, len); |
| return OPAL_UNSUPPORTED; |
| } |
| |
| static int lid_files_erase(struct blocklevel_device *bl __unused, |
| uint64_t pos, uint64_t len) |
| { |
| |
| prlog(PR_ERR, "lid files erase at 0x%llx for 0x%llx\n", |
| pos, len); |
| return OPAL_UNSUPPORTED; |
| } |
| |
| static int get_lid_files_info(struct blocklevel_device *bl, |
| const char **name, uint64_t *total_size, |
| uint32_t *erase_granule) |
| { |
| struct pldm_ctx_data *ctx; |
| |
| ctx = container_of(bl, struct pldm_ctx_data, bl); |
| ctx->bl.erase_mask = ctx->erase_granule - 1; |
| |
| if (name) |
| *name = NULL; |
| if (total_size) |
| *total_size = ctx->total_size; |
| if (erase_granule) |
| *erase_granule = ctx->erase_granule; |
| |
| return OPAL_SUCCESS; |
| } |
| |
| bool pldm_lid_files_exit(struct blocklevel_device *bl) |
| { |
| struct pldm_ctx_data *ctx; |
| struct pldm_lid *lid, *tmp; |
| |
| if (bl) { |
| ctx = container_of(bl, struct pldm_ctx_data, bl); |
| free(ctx); |
| } |
| |
| /* free all lid entries */ |
| list_for_each_safe(&lid_files, lid, tmp, list) |
| free(lid); |
| |
| if (raw_hdr) |
| free(raw_hdr); |
| |
| return true; |
| } |
| |
| int pldm_lid_files_init(struct blocklevel_device **bl) |
| { |
| struct pldm_ctx_data *ctx; |
| uint32_t lid_files_count; |
| int rc; |
| |
| if (!bl) |
| return FLASH_ERR_PARM_ERROR; |
| |
| *bl = NULL; |
| |
| ctx = zalloc(sizeof(struct pldm_ctx_data)); |
| if (!ctx) |
| return FLASH_ERR_MALLOC_FAILED; |
| |
| init_lock(&ctx->lock); |
| |
| ctx->bl.read = &lid_files_read; |
| ctx->bl.write = &lid_files_write; |
| ctx->bl.erase = &lid_files_erase; |
| ctx->bl.get_info = &get_lid_files_info; |
| ctx->bl.exit = &pldm_lid_files_exit; |
| |
| /* convert lid ids data to pnor structure */ |
| rc = lid_ids_to_vaddr_mapping(); |
| if (rc) |
| goto err; |
| |
| lid_files_count = get_lids_count(); |
| |
| prlog(PR_NOTICE, "Number of lid files: %d\n", lid_files_count); |
| print_lid_files_attr(); |
| |
| ctx->total_size = lid_files_count * VMM_SIZE_RESERVED_PER_SECTION; |
| ctx->erase_granule = ERASE_GRANULE_DEF; |
| |
| ctx->bl.keep_alive = 0; |
| |
| *bl = &(ctx->bl); |
| |
| return OPAL_SUCCESS; |
| |
| err: |
| free(ctx); |
| return rc; |
| } |