[PATCH 7/9] lib/flash: Add support for ATTR_PERM partition on BMC machines

Samuel Mendoza-Jonas sam.mj at au1.ibm.com
Tue Dec 15 14:15:28 AEDT 2015


Attribute overrides set by the user are written to the host PNOR, and
existing overrides need to be read from the PNOR on startup. These are
stored in the ATTR_PERM partition.

When populated the ATTR_PERM partiton is divided into several Attribute
Sections defining a 'layer' (only LAYER_PERM in this case) and a chunk
size. Each chunk within a section may include one or more Attribute
Override headers and associated data section.
The ATTR_PERM partition is defined as big-endian, and is also ECC
protected (this is abstracted away by the libflash library).

Signed-off-by: Samuel Mendoza-Jonas <sam.mj at au1.ibm.com>
---
 lib/flash/flash.c | 543 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/flash/flash.h |  22 +++
 2 files changed, 565 insertions(+)

diff --git a/lib/flash/flash.c b/lib/flash/flash.c
index 6705776..3450758 100644
--- a/lib/flash/flash.c
+++ b/lib/flash/flash.c
@@ -19,6 +19,25 @@
 #include <libflash/ecc.h>
 
 
+/* Attribute Header constants */
+#define		ATTR_POS_NA		0xffff
+#define		ATTR_UNIT_POS_NA	0xff
+#define		ATTR_NODE_NA		0xf
+
+/* Possible Attribute Override sections in the ATTR_PERM and ATTR_TEMP
+ * partitions of the PNOR. */
+struct attribute_section {
+	enum layer {
+		LAYER_NONE,
+		LAYER_FAPI,
+		LAYER_TARG,
+		LAYER_PERM,
+		LAYER_LAST = LAYER_PERM,
+		LAYER_TERM = 0xFFFFFFFF,
+	} iv_layer;
+	size_t		iv_size;
+};
+
 struct flash_info {
 	/* Device information */
 	struct blocklevel_device	*bl;
@@ -38,6 +57,133 @@ struct flash_info {
 	uint32_t			len;
 };
 
+static void dump_attribute(struct attribute *attr)
+{
+	unsigned int i;
+
+	pb_log("Dumping Attribute:\n");
+	pb_log("-------------------------\n");
+	pb_log("\t.iv_attrID\t%x\n", attr->hdr->iv_attrID);
+	pb_log("\t.iv_targetType\t%x\n", attr->hdr->iv_targetType);
+	pb_log("\t.iv_pos\t\t%x\n", attr->hdr->iv_pos);
+	pb_log("\t.iv_unitPos\t%x\n", attr->hdr->iv_unitPos);
+	pb_log("\t.iv_node\t%x\n", attr->hdr->iv_node);
+	pb_log("\t.iv_flags\t%x\n", attr->hdr->iv_flags);
+	pb_log("\t.iv_valSize\t%u\n", attr->hdr->iv_valSize);
+	pb_log("Data Section:\n\t");
+	if (attr->data) {
+		for (i = 0; i < attr->hdr->iv_valSize; i++)
+			pb_log("%x ", attr->data[i]);
+	} else {
+		pb_log("(NULL data section)\n");
+	}
+	pb_log("\n-------------------------\n");
+}
+
+/* Initialise a LAYER_PERM section in a ATTR_PERM buffer */
+static int stamp_header(struct flash_info *info, uint32_t pos, size_t size)
+{
+
+	uint8_t *buf;
+	size_t total_size;
+	struct attribute_section section = {
+		.iv_layer	= LAYER_PERM,
+		.iv_size	= size,
+	};
+
+	if (!info->buffer) {
+		pb_log("%s: No buffer to stamp\n", __func__);
+		return -1;
+	}
+
+	if (__be32_to_cpu(*(uint32_t *)(&info->buffer[pos]) == LAYER_PERM)) {
+		pb_log("%s: Existing header at position %u\n", __func__, pos);
+		return 0;
+	}
+
+	total_size = sizeof(struct attribute_section) + size;
+	if (pos + total_size >= info->len) {
+		pb_log("%s: New chunk would exceed buffer size (pos %u, sz %zu)\n",
+		       __func__, pos, section.iv_size);
+		return -1;
+	}
+
+	buf = (uint8_t *)(&info->buffer[pos]);
+	*(uint32_t *)buf = __cpu_to_be32(section.iv_layer);
+	buf += 4;
+	/* 4 bytes of padding */
+	*(uint32_t *)buf = 0x00000000;
+	buf += 4;
+	*(uint64_t *)buf = __cpu_to_be64(section.iv_size);
+	buf += 8;
+
+	/* Initialise chunk. */
+	/*
+	memset(buf, 0, section.iv_size);
+	buf += section.iv_size;
+	*/
+
+	/* Explicitly mark the end of valid memory if able */
+	/*
+	if (total_size + sizeof(uint64_t) <= info->len)
+		*(uint64_t *)buf = __cpu_to_be64(LAYER_TERM);
+	*/
+
+	return 0;
+}
+
+/*
+ * Return the next valid attribute section (ie. PERM) and its offset.
+ * If no section is found, return th next available empty offset.
+ * Return LAYER_TERM on error
+ */
+static uint32_t next_layer_perm(void *ctx, struct flash_info *info,
+			uint32_t idx, struct attribute_section **section)
+{
+
+	bool found = false;
+	uint32_t i, pos;
+	size_t sz = sizeof(uint64_t);	/* Offset to .iv_size */
+
+	for (i = idx; i < info->len; i++) {
+		pos = __be32_to_cpu(*(uint32_t *)(&info->buffer[i]));
+		switch (pos) {
+		case LAYER_FAPI:
+		case LAYER_TARG:
+			pos = __be64_to_cpu(*(uint64_t *)(&info->buffer[i+sz]));
+			pb_debug("%s: Skipping unsupported layer, size %u\n",
+				 __func__, pos);
+			i += pos;
+			break;
+		case LAYER_PERM:
+			found = true;
+			break;
+		case LAYER_NONE:
+		case LAYER_TERM:
+			pb_debug("%s: Empty space at %u\n", __func__, i);
+			return i;
+		default:
+			pb_log("Unrecognised header %x @ %u, wrong offset?\n",
+			       pos, i);
+			return LAYER_TERM;
+		}
+		if (found)
+			break;
+	}
+
+	if (!found) {
+		pb_log("Reached end of buffer without LAYER_TERM\n");
+		return LAYER_TERM;
+	}
+
+	*section = talloc(ctx, struct attribute_section);
+	(*section)->iv_size = __be64_to_cpu(*(uint64_t *)(&info->buffer[i+sz]));
+	(*section)->iv_layer = LAYER_PERM;
+	pb_debug("Found LAYER_PERM at %u, size %zu\n", i, (*section)->iv_size);
+
+	return i + sizeof(struct attribute_section);
+}
+
 //TODO May need to consider flash sides
 static struct flash_info *flash_setup(void *ctx, const char *partition,
 				      bool ecc)
@@ -127,6 +273,403 @@ out:
 	return NULL;
 }
 
+/* Read an attribute header and associated data section from buffer */
+static inline void parse_attribute(void *ctx, char *buf, struct attribute *attr)
+{
+	uint8_t tmp;
+
+	/* Retrieve header section */
+	attr->hdr = talloc(ctx, struct attribute_header);
+	attr->hdr->iv_attrID = __be32_to_cpu(*(uint32_t *)buf);
+	buf += 4;
+	attr->hdr->iv_targetType = __be32_to_cpu(*(uint32_t *)buf);
+	buf += 4;
+	attr->hdr->iv_pos = __be16_to_cpu(*(uint16_t *)buf);
+	buf += 2;
+	attr->hdr->iv_unitPos = *(uint8_t *)buf;
+	buf += 1;
+
+	tmp = *(uint8_t *)buf;
+	buf += 1;
+	attr->hdr->iv_node = (tmp & 0xf0) >> 4;
+	attr->hdr->iv_flags = (tmp & 0x0f);
+
+	attr->hdr->iv_valSize = __be32_to_cpu(*(uint32_t *)buf);
+	buf += 4;
+
+	/* Retrieve data section */
+	attr->data = talloc_array(ctx, uint8_t, attr->hdr->iv_valSize);
+	switch (attr->hdr->iv_valSize) {
+	case 1:
+		*attr->data = *(uint8_t *)buf;
+		break;
+	case 2:
+		*(uint16_t *)attr->data = __be16_to_cpu(*(uint16_t *)buf);
+		break;
+	case 4:
+		*(uint32_t *)attr->data = __be32_to_cpu(*(uint32_t *)buf);
+		break;
+	case 8:
+		*(uint64_t *)attr->data = __be64_to_cpu(*(uint64_t *)buf);
+		break;
+	default:
+		pb_log("Attribute '%x' has unsupported value size: %u\n",
+		       attr->hdr->iv_attrID, attr->hdr->iv_valSize);
+		talloc_free(attr->data);
+		attr->data = NULL;
+		break;
+	}
+}
+
+static inline bool attribute_matches(struct attribute_header hdr_a,
+				struct attribute_header hdr_b)
+{
+	if (hdr_a.iv_attrID != hdr_b.iv_attrID)
+		return false;
+
+	return hdr_a.iv_targetType == hdr_b.iv_targetType &&
+		(hdr_a.iv_pos == hdr_b.iv_pos ||
+			hdr_a.iv_pos == ATTR_POS_NA) &&
+		(hdr_a.iv_unitPos == hdr_b.iv_unitPos ||
+			hdr_a.iv_unitPos == ATTR_UNIT_POS_NA) &&
+		(hdr_a.iv_node == hdr_b.iv_node ||
+			hdr_a.iv_node == ATTR_NODE_NA);
+}
+
+/*
+ * Attempt to find an attribute ID in the partition.
+ * Returns true if the attribute is found, and offset is set to the start
+ * of the struct.
+ * Otherwise returns false and offset is set to the next writable area that
+ * will fit the attribute.
+ */
+static bool find_attribute_location(struct flash_info *info,
+			   struct attribute_header *hdr, uint32_t *offset)
+{
+	struct attribute_section *section = NULL;
+	uint32_t i, j, pos, last_valid;
+	struct attribute tmp;
+	bool match = false;
+
+	uint32_t req_sz = sizeof(struct attribute_header) + hdr->iv_valSize;
+
+	*offset = i = last_valid =  0;
+
+	while (i < info->len) {
+		pos = next_layer_perm(info, info, i, &section);
+
+		if (pos == LAYER_TERM) {
+			/* Encountered an error, bail out */
+			break;
+		}
+		if (!section) {
+			/* No more sections to parse - return last usable space
+			 * or location of new section */
+			if (!last_valid && stamp_header(info, pos, req_sz) == 0)
+				*offset = pos + sizeof(struct attribute_section);
+			break;
+		}
+
+		for (j = pos; j < pos + section->iv_size;) {
+			parse_attribute(info, &info->buffer[j], &tmp);
+
+			/* Empty space in current section */
+			/* It's not clear that Hostboot handles empty space
+			 * within a section well - avoid for now */
+			/*
+			if (tmp.hdr->iv_attrID == 0) {
+				if (section->iv_size - (j - pos) >= req_sz) {
+					last_valid = j;
+				}
+				break;
+			}
+			*/
+
+			/* Existing attribute */
+			if (attribute_matches(*hdr, *tmp.hdr)) {
+				*offset = j;
+				match = true;
+				goto out;
+			}
+			j += sizeof(struct attribute_header) + tmp.hdr->iv_valSize;
+		}
+		i = pos + section->iv_size;
+		talloc_free(section);
+		section = NULL;
+	}
+
+out:
+	if (!match && last_valid) {
+		*offset = last_valid;
+		pb_debug("%s: Using existing space @ %u\n", __func__, *offset);
+	}
+
+	return match;
+}
+
+/* Convert a firmware_attribute struct formed from XML data to an
+ * attribute struct resembling the ATTR_PERM layout */
+static struct attribute *init_attribute(void *ctx, struct firmware_attr *attr)
+{
+	struct attribute *attribute;
+	long int value;
+	if (!attr) {
+		pb_log("%s: No source attribute!\n", __func__);
+		return NULL;
+	}
+
+	attribute = talloc(ctx, struct attribute);
+	if (!attribute) {
+		pb_log("%s: Unable to allocate attribute\n", __func__);
+		return NULL;
+	}
+	attribute->hdr = talloc(attribute, struct attribute_header);
+	if (!attribute->hdr) {
+		pb_log("%s: Unable to allocate header\n", __func__);
+		talloc_free(attribute);
+		return NULL;
+	}
+
+	attribute->hdr->iv_attrID = strtoul(attr->numeric_id, NULL, 0);
+	attribute->hdr->iv_targetType = attr->type;
+	attribute->hdr->iv_pos =  attr->pos;
+	attribute->hdr->iv_unitPos = attr->unitPos;
+	attribute->hdr->iv_node = attr->node;
+	attribute->hdr->iv_flags = attr->flags;
+	attribute->hdr->iv_valSize = attr->size;
+
+	errno = 0;
+	if (attr->is_signed)
+		value = strtoul(attr->value, NULL, 0);
+	else
+		value = strtol(attr->value, NULL, 0);
+	if (errno) {
+		pb_log("Error serialising value '%s': %m\n", attr->value);
+		talloc_free(attribute);
+		return NULL;
+	}
+
+	attribute->data = talloc_array(attribute, uint8_t,
+				       attribute->hdr->iv_valSize);
+	memcpy(attribute->data, attr->value, attribute->hdr->iv_valSize);
+	memcpy(attribute->data,
+	       (uint8_t *)&value, attribute->hdr->iv_valSize);
+
+	return attribute;
+}
+
+/* Write an attribute in serialised format. Writes only the data section if
+ * attribute already exists */
+static int write_attribute(char *buf, struct attribute *attr, bool update)
+{
+	struct attribute_header *hdr;
+	int rc = 0;
+
+	if (!update) {
+		/* Write header section */
+		*(uint32_t *)buf = __cpu_to_be32(attr->hdr->iv_attrID);
+		buf += 4;
+		*(uint32_t *)buf = __cpu_to_be32(attr->hdr->iv_targetType);
+		buf += 4;
+		*(uint16_t *)buf = __cpu_to_be16(attr->hdr->iv_pos);
+		buf += 2;
+		*(uint8_t *)buf = attr->hdr->iv_unitPos;
+		buf += 1;
+
+		*(uint8_t *)buf = 0;
+		*(uint8_t *)buf |= attr->hdr->iv_node << 4;
+		*(uint8_t *)buf |= attr->hdr->iv_flags;
+		buf += 1;
+
+		*(uint32_t *)buf = __cpu_to_be32(attr->hdr->iv_valSize);
+		buf += 4;
+	} else {
+		pb_debug("Attribute already exists, updating in place\n");
+		hdr = (struct attribute_header *)buf;
+		if (__be32_to_cpu(hdr->iv_valSize) != attr->hdr->iv_valSize) {
+			pb_log("%s: Size of existing attribute %x does not match!",
+			       __func__, attr->hdr->iv_attrID);
+			pb_log("%s: Trying to write %u bytes to %u byte field"
+			       " - aborting", __func__, attr->hdr->iv_valSize,
+			       __be32_to_cpu(hdr->iv_valSize));
+			rc = -1;
+			goto out;
+		}
+
+		buf += sizeof(struct attribute_header);
+	}
+
+	/* Write data section */
+	switch (attr->hdr->iv_valSize) {
+	case 1:
+		*(uint8_t *)buf = *attr->data;
+		break;
+	case 2:
+		*(uint16_t *)buf = __cpu_to_be16(*(uint16_t *)attr->data);
+		break;
+	case 4:
+		*(uint32_t *)buf = __cpu_to_be32(*(uint32_t *)attr->data);
+		break;
+	case 8:
+		*(uint64_t *)buf = __cpu_to_be64(*(uint64_t *)attr->data);
+		break;
+	default:
+		pb_log("Unsupported attribute value size: %u\n",
+		       attr->hdr->iv_valSize);
+		rc = -1;
+		break;
+	}
+
+out:
+	return rc;
+}
+
+int flash_write_attribute(void *ctx, struct firmware_attr *attributes,
+			  int n_attrs)
+{
+	struct firmware_attr *current;
+	uint32_t pos = 0, bufsz = 0;
+	struct flash_info *info;
+	struct attribute *attr;
+	int rc = 0, i;
+	bool found;
+
+	info = flash_setup(ctx, "ATTR_PERM", true);
+	if (!info)
+		return -1;
+
+	/* Serialise each attribute into the buffer */
+	for (i = 0; i < n_attrs; i++) {
+		current = &attributes[i];
+		bufsz += sizeof(struct attribute_header) + current->size;
+		pb_debug("Writing %zu bytes for attribute %s: %s, "
+		       "%u bytes for data section (total %u)\n",
+		       sizeof(struct attribute_header),
+		       current->id, current->numeric_id, current->size,
+		       bufsz);
+
+		attr = init_attribute(info, current);
+
+		found = find_attribute_location(info, attr->hdr, &pos);
+		if (!pos) {
+			pb_log("%s: Unable to find suitable location\n",
+			       __func__);
+			talloc_free(attr);
+			rc = -1;
+			goto out;
+		}
+
+		pb_debug("Writing attibute at byte %u\n", pos);
+		rc = write_attribute(&info->buffer[pos], attr, found);
+		if (rc) {
+			talloc_free(attr);
+			goto out;
+		}
+	}
+
+	/* Commit modified buffer back to flash */
+	rc = blocklevel_smart_write(info->bl, info->attr_data_pos,
+				    info->buffer, info->len);
+	if (rc) {
+		pb_log("Failed to write attribute data\n");
+		pb_log("WARNING: Touched flash at ATTR_PERM should be erased/reset\n");
+	}
+
+out:
+	arch_flash_close(info->bl, NULL);
+	talloc_free(info);
+
+	return rc;
+}
+
+int flash_read_attrs(void *ctx, struct attribute **attrs)
+{
+	struct attribute_section *section = NULL;
+	struct attribute *tmp = NULL;
+	struct flash_info *info;
+	uint32_t i = 0, j, offset, pos, n_attrs = 0;
+
+	info = flash_setup(ctx, "ATTR_PERM", true);
+	if (!info)
+		return -1;
+
+	/* Loop through available sections */
+	while (i < info->len) {
+		offset = next_layer_perm(info, info, i, &section);
+		if (!section)
+			break;
+
+		/* Loop through attributes contained within section */
+		for (j = offset; j < section->iv_size + offset; n_attrs++) {
+			pos = __be32_to_cpu(*(uint32_t *)(&info->buffer[j]));
+			if (pos == LAYER_TERM || pos == LAYER_NONE)
+				break;
+
+			tmp = talloc_realloc(ctx, tmp, struct attribute,
+					       n_attrs + 1);
+			if (!tmp) {
+				pb_log("Error reallocating space for attributes\n");
+				break;
+			}
+			parse_attribute(ctx, &info->buffer[j], &tmp[n_attrs]);
+			j += sizeof(struct attribute_header) +
+				tmp[n_attrs].hdr->iv_valSize;
+		}
+		i = offset + section->iv_size;
+		talloc_free(section);
+		section = NULL;
+	}
+
+	pb_debug("%s: %d existing attribute%s found\n", __func__, n_attrs,
+	       n_attrs == 1 ? "" : "s");
+	for (j = 0; j < n_attrs; j++)
+		dump_attribute(&tmp[j]);
+
+	arch_flash_close(info->bl, NULL);
+	talloc_free(info);
+
+	*attrs = tmp;
+
+	return n_attrs;
+}
+
+char *parse_attribute_value(void *ctx, struct attribute attr, bool is_signed)
+{
+	uint32_t size;
+	char *fmt, *value;
+
+	if (!attr.hdr || !attr.data) {
+		pb_log("%s: NULL attribute\n", __func__);
+		return NULL;
+	}
+
+	size = attr.hdr->iv_valSize;
+	switch (size) {
+	case 1:
+		fmt = talloc_asprintf(ctx, is_signed ? "%%hhd" : "%%hhu");
+		value = talloc_asprintf(ctx, fmt, attr.data);
+		break;
+	case 2:
+		fmt = talloc_asprintf(ctx, is_signed ? "%%hd" : "%%hu");
+		value = talloc_asprintf(ctx, fmt, *(short int *)attr.data);
+		break;
+	case 4:
+		fmt = talloc_asprintf(ctx, is_signed ? "%%d" : "%%u");
+		value = talloc_asprintf(ctx, fmt, *(int *)attr.data);
+		break;
+	case 8:
+		fmt = talloc_asprintf(ctx, is_signed ? "%%ld" : "%%lu");
+		value = talloc_asprintf(ctx, fmt, *(long long int *)attr.data);
+		break;
+	default:
+		pb_log("Unsupported data type, size %u\n", size);
+		return NULL;
+	}
+
+	talloc_free(fmt);
+	return value;
+}
+
 int flash_read_version(void *ctx, char ***versions)
 {
 	char *saveptr, *tok,  **tmp;
diff --git a/lib/flash/flash.h b/lib/flash/flash.h
index 103f1e4..6275508 100644
--- a/lib/flash/flash.h
+++ b/lib/flash/flash.h
@@ -21,6 +21,28 @@
 #include <flash/config.h>
 #include <types/types.h>
 
+/* Attribute header fields (attributeTank.H) */
+struct attribute_header {
+	uint32_t	iv_attrID;	/* xml: numeric-id */
+	uint32_t	iv_targetType;	/* xml: type */
+	uint16_t	iv_pos;		/* xml: position */
+	uint8_t		iv_unitPos;	/* xml: unit */
+	uint8_t		iv_node  : 4;	/* xml: node */
+	uint8_t		iv_flags : 4;	/* xml: flags */
+	uint32_t	iv_valSize;	/* xml: size */
+};
+
+/* Internal */
+struct attribute {
+	int flags; /* eg. PROC_PCIE_IOP_SWAP, not sure where this applies */
+	struct attribute_header	*hdr;
+	uint8_t		*data;
+};
+
+char *parse_attribute_value(void *ctx, struct attribute attr, bool is_signed);
+
+int flash_write_attribute(void *ctx, struct firmware_attr *attr, int n_attrs);
+int flash_read_attrs(void *ctx, struct attribute **attrs);
 int flash_read_version(void *ctx, char ***versions);
 
 #endif /* FLASH_H */
-- 
2.6.3



More information about the Petitboot mailing list