433 lines
10 KiB
C
433 lines
10 KiB
C
// 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;
|
|
}
|