[Skiboot] [RFC] libflash/libffs: Rework libffs
Cyril Bur
cyril.bur at au1.ibm.com
Tue Mar 22 14:39:43 AEDT 2016
This patch attempts a rework of libffs to prepare it for future changes.
Firstly the types are split in two:
1. Packed, big endian structures used to map exactly how the data is on flash.
2. CPU endian, sane valued, not packed structures used to manipulate FFS data.
Secondly:
The packed struct can use BE types so that in future tools like sparse can be
run over the code to check for endian conversion bugs.
Thirdly:
defines of sizeof(struct ...) were removed for clarity.
Finally:
For ease of manipulation, the in memory FFS structures contain a linked list of
entries as this will make addition and removal operations much easier.
This patch should be invisible to consumers of libffs.
Signed-off-by: Cyril Bur <cyril.bur at au1.ibm.com>
---
libflash/ffs.h | 139 +++++++++++++++++++--------
libflash/libffs.c | 281 +++++++++++++++++++++++++++++-------------------------
libflash/libffs.h | 11 +++
3 files changed, 262 insertions(+), 169 deletions(-)
diff --git a/libflash/ffs.h b/libflash/ffs.h
index 5cb6778..3d1ac8f 100644
--- a/libflash/ffs.h
+++ b/libflash/ffs.h
@@ -28,8 +28,11 @@
#if defined(__KERNEL__)
#include <linux/types.h>
#else
+#define CCAN_SHORT_TYPES_H
+#include <ccan/endian/endian.h>
#include <stdint.h>
#endif
+#include <ccan/list/list.h>
/* The version of this partition implementation */
#define FFS_VERSION_1 1
@@ -40,25 +43,13 @@
/* The maximum length of the partition name */
#define PART_NAME_MAX 15
-/*
- * Sizes of the data structures
- */
-#define FFS_HDR_SIZE sizeof(struct ffs_hdr)
-#define FFS_ENTRY_SIZE sizeof(struct ffs_entry)
-
-/*
- * Sizes of the data structures w/o checksum
- */
-#define FFS_HDR_SIZE_CSUM (FFS_HDR_SIZE - sizeof(uint32_t))
-#define FFS_ENTRY_SIZE_CSUM (FFS_ENTRY_SIZE - sizeof(uint32_t))
-
/* pid of logical partitions/containers */
#define FFS_PID_TOPLEVEL 0xFFFFFFFF
/*
* Type of image contained w/in partition
*/
-enum type {
+enum ffs_type {
FFS_TYPE_DATA = 1,
FFS_TYPE_LOGICAL = 2,
FFS_TYPE_PARTITION = 3,
@@ -74,7 +65,9 @@ enum type {
#define FFS_ENRY_INTEG_ECC 0x8000
/**
- * struct ffs_entry_user - User data enties
+ * struct __ffs_entry_user - On flash user data entries
+ *
+ * Represents the on flash layout of FFS structures
*
* @chip: Chip Select (0,1)
* @compressType: Compression Indication/alg (0=not compressed)
@@ -84,18 +77,37 @@ enum type {
* @freeMisc[2]: Unused Miscellaneious Info
* @freeUser[14]: Unused User Data
*/
+struct __ffs_entry_user {
+ uint8_t chip;
+ uint8_t compresstype;
+ be16 datainteg;
+ uint8_t vercheck;
+ uint8_t miscflags;
+ uint8_t freemisc[2];
+ be32 reserved[14];
+} __attribute__ ((packed));
+
+/**
+ * struct ffs_entry_user - User data entries
+ *
+ * @chip: Chip Select (0,1)
+ * @compressType: Compression Indication/alg (0=not compressed)
+ * @dataInteg: Indicates Data Integrity mechanism
+ * @verCheck: Indicates Version check type
+ * @miscFlags: Misc Partition related Flags
+ */
struct ffs_entry_user {
- uint8_t chip;
- uint8_t compresstype;
+ uint8_t chip;
+ uint8_t compresstype;
uint16_t datainteg;
- uint8_t vercheck;
- uint8_t miscflags;
- uint8_t freemisc[2];
- uint32_t reserved[14];
+ uint8_t vercheck;
+ uint8_t miscflags;
};
/**
- * struct ffs_entry - Partition entry
+ * struct __ffs_entry - On flash partition entry
+ *
+ * Represents the on flash layout of FFS structures
*
* @name: Opaque null terminated string
* @base: Starting offset of partition in flash (in hdr.block_size)
@@ -109,22 +121,50 @@ struct ffs_entry_user {
* @user: User data (optional)
* @checksum: Partition entry checksum (includes all above)
*/
+struct __ffs_entry {
+ char name[PART_NAME_MAX + 1];
+ be32 base;
+ be32 size;
+ be32 pid;
+ be32 id;
+ be32 type;
+ be32 flags;
+ be32 actual;
+ be32 resvd[4];
+ struct __ffs_entry_user user;
+ /* The checksum is actually endian agnostic */
+ uint32_t checksum;
+} __attribute__ ((packed));
+
+/**
+ * struct ffs_entry - Partition entry
+ *
+ * @name: Opaque null terminated string
+ * @base: Starting offset of partition in flash (in bytes)
+ * @size: Partition size (in bytes)
+ * @actual: Actual partition size (in bytes)
+ * @pid: Parent partition entry (FFS_PID_TOPLEVEL for toplevel)
+ * @type: Describe type of partition
+ * @flags: Partition attributes (optional)
+ * @user: User data (optional)
+ */
struct ffs_entry {
- char name[PART_NAME_MAX + 1];
+ char name[PART_NAME_MAX + 1];
uint32_t base;
uint32_t size;
+ uint32_t actual;
uint32_t pid;
- uint32_t id;
- uint32_t type;
+ enum ffs_type type;
uint32_t flags;
- uint32_t actual;
- uint32_t resvd[4];
struct ffs_entry_user user;
- uint32_t checksum;
-} __attribute__ ((packed));
+ struct list_node list;
+};
+
/**
- * struct ffs_hdr - FSP Flash Structure header
+ * struct __ffs_hdr - On flash FSP Flash Structure header
+ *
+ * Represents the on flash layout of FFS structures
*
* @magic: Eye catcher/corruption detector
* @version: Version of the structure
@@ -133,22 +173,39 @@ struct ffs_entry {
* @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
- * @resvd: Reserved words for future use
+ * @resvd[4]: Reserved words for future use
* @checksum: Header checksum
* @entries: Pointer to array of partition entries
*/
-struct ffs_hdr {
- uint32_t magic;
- uint32_t version;
- uint32_t size;
- uint32_t entry_size;
- uint32_t entry_count;
- uint32_t block_size;
- uint32_t block_count;
- uint32_t resvd[4];
- uint32_t checksum;
- struct ffs_entry entries[];
+struct __ffs_hdr {
+ be32 magic;
+ be32 version;
+ be32 size;
+ be32 entry_size;
+ be32 entry_count;
+ be32 block_size;
+ be32 block_count;
+ be32 resvd[4];
+ /* The checksum is actually endian agnostic */
+ uint32_t checksum;
+ struct __ffs_entry entries[];
} __attribute__ ((packed));
+/**
+ * struct ffs_hdr - FSP Flash Structure header
+ *
+ * @version: Version of the structure
+ * @size: Size of partition table (in bytes)
+ * @block_size: Size of block on device (in bytes)
+ * @block_count: Number of blocks on device
+ * @entries: List of partition entries
+ */
+struct ffs_hdr {
+ uint32_t version;
+ uint32_t size;
+ uint32_t block_size;
+ uint32_t block_count;
+ struct list_head entries;
+};
#endif /* __FFS_H__ */
diff --git a/libflash/libffs.c b/libflash/libffs.c
index 4d57992..2c62cd0 100644
--- a/libflash/libffs.c
+++ b/libflash/libffs.c
@@ -25,22 +25,14 @@
#include <unistd.h>
#endif
-#include <ccan/endian/endian.h>
-
#include "libffs.h"
-enum ffs_type {
- ffs_type_flash,
- ffs_type_image,
-};
-
struct ffs_handle {
struct ffs_hdr hdr; /* Converted header */
- enum ffs_type type;
uint32_t toc_offset;
uint32_t max_size;
- void *cache;
- uint32_t cached_size;
+ /* The converted header knows how big this is */
+ struct __ffs_hdr *cache;
struct blocklevel_device *bl;
};
@@ -53,32 +45,103 @@ static uint32_t ffs_checksum(void* data, size_t size)
return csum;
}
-static int ffs_check_convert_header(struct ffs_hdr *dst, struct ffs_hdr *src)
+/* Helper functions for typesafety and size safety */
+static uint32_t ffs_hdr_checksum(struct __ffs_hdr *hdr)
+{
+ return ffs_checksum(hdr, sizeof(struct __ffs_hdr));
+}
+
+static uint32_t ffs_entry_checksum(struct __ffs_entry *ent)
{
- dst->magic = be32_to_cpu(src->magic);
- if (dst->magic != FFS_MAGIC)
+ return ffs_checksum(ent, sizeof(struct __ffs_entry));
+}
+
+static int ffs_check_convert_header(struct ffs_hdr *dst, struct __ffs_hdr *src)
+{
+ if (be32_to_cpu(src->magic) != FFS_MAGIC)
return FFS_ERR_BAD_MAGIC;
dst->version = be32_to_cpu(src->version);
if (dst->version != FFS_VERSION_1)
return FFS_ERR_BAD_VERSION;
- if (ffs_checksum(src, FFS_HDR_SIZE) != 0)
+ if (ffs_hdr_checksum(src) != 0)
return FFS_ERR_BAD_CKSUM;
- dst->size = be32_to_cpu(src->size);
- dst->entry_size = be32_to_cpu(src->entry_size);
- dst->entry_count = be32_to_cpu(src->entry_count);
+ if (be32_to_cpu(src->entry_size) != sizeof(struct __ffs_entry))
+ return FFS_ERR_BAD_SIZE;
dst->block_size = be32_to_cpu(src->block_size);
+ dst->size = be32_to_cpu(src->size) * dst->block_size;
dst->block_count = be32_to_cpu(src->block_count);
return 0;
}
+static int ffs_entry_to_flash(struct ffs_hdr *hdr,
+ struct __ffs_entry *dst, struct ffs_entry *src)
+{
+ int index = 1; /* On flash indexes start at 1 */
+ struct ffs_entry *ent;
+ list_for_each(&hdr->entries, ent, list) {
+ if (ent == src)
+ break;
+ index++;
+ }
+
+ if (!ent)
+ return FFS_ERR_PART_NOT_FOUND;
+
+ memcpy(dst->name, src->name, sizeof(dst->name));
+ dst->name[PART_NAME_MAX] = '\0';
+ dst->base = cpu_to_be32(src->base * hdr->block_size);
+ dst->size = cpu_to_be32(src->size * hdr->block_size);
+ dst->pid = cpu_to_be32(src->pid);
+ dst->id = cpu_to_be32(index);
+ dst->type = cpu_to_be32(src->type); /* Check that it is valid? */
+ dst->flags = cpu_to_be32(src->flags);
+ dst->actual = cpu_to_be32(src->actual);
+ dst->user.datainteg = cpu_to_be16(src->user.datainteg);
+ dst->checksum = ffs_entry_checksum(dst);
+
+ return 0;
+}
+
+static int ffs_entry_to_cpu(struct ffs_hdr *hdr,
+ struct ffs_entry *dst, struct __ffs_entry *src)
+{
+ if (ffs_entry_checksum(src) != 0)
+ return FFS_ERR_BAD_CKSUM;
+ memcpy(dst->name, src->name, sizeof(dst->name));
+ dst->name[PART_NAME_MAX] = '\0';
+ dst->base = be32_to_cpu(src->base) * hdr->block_size;
+ dst->size = be32_to_cpu(src->size) * hdr->block_size;
+ dst->actual = be32_to_cpu(src->actual);
+ dst->pid = be32_to_cpu(src->pid);
+ dst->type = be32_to_cpu(src->type); /* Check that it is valid? */
+ dst->flags = be32_to_cpu(src->flags);
+ dst->user.datainteg = be16_to_cpu(src->user.datainteg);
+ return 0;
+}
+
+static struct ffs_entry *ffs_get_part(struct ffs_handle *ffs, uint32_t index)
+{
+ int i = 0;
+ struct ffs_entry *ent;
+
+ list_for_each(&ffs->hdr.entries, ent, list) {
+ if (i == index)
+ break;
+ i++;
+ }
+
+ return ent;
+}
+
int ffs_init(uint32_t offset, uint32_t max_size, struct blocklevel_device *bl,
struct ffs_handle **ffs, bool mark_ecc)
{
- struct ffs_hdr hdr;
- struct ffs_hdr blank_hdr;
+ struct __ffs_hdr blank_hdr;
+ struct __ffs_hdr raw_hdr;
struct ffs_handle *f;
uint32_t total_size;
+ struct ffs_entry *ent;
int rc, i;
if (!ffs || !bl)
@@ -97,7 +160,7 @@ int ffs_init(uint32_t offset, uint32_t max_size, struct blocklevel_device *bl,
return FLASH_ERR_PARM_ERROR;
/* Read flash header */
- rc = blocklevel_read(bl, offset, &hdr, sizeof(hdr));
+ rc = blocklevel_read(bl, offset, &raw_hdr, sizeof(raw_hdr));
if (rc) {
FL_ERR("FFS: Error %d reading flash header\n", rc);
return rc;
@@ -106,17 +169,17 @@ int ffs_init(uint32_t offset, uint32_t max_size, struct blocklevel_device *bl,
/*
* Flash controllers can get deconfigured or otherwise upset, when this
* happens they return all 0xFF bytes.
- * An ffs_hdr consisting of all 0xFF cannot be valid and it would be
+ * An __ffs_hdr consisting of all 0xFF cannot be valid and it would be
* nice to drop a hint to the user to help with debugging. This will
* help quickly differentiate between flash corruption and standard
* type 'reading from the wrong place' errors vs controller errors or
* reading erased data.
*/
- memset(&blank_hdr, UINT_MAX, sizeof(struct ffs_hdr));
- if (memcmp(&blank_hdr, &hdr, sizeof(struct ffs_hdr)) == 0) {
+ memset(&blank_hdr, UINT_MAX, sizeof(struct __ffs_hdr));
+ if (memcmp(&blank_hdr, &raw_hdr, sizeof(struct __ffs_hdr)) == 0) {
FL_ERR("FFS: Reading the flash has returned all 0xFF.\n");
- FL_ERR("Are you reading erased flash?\n");
- FL_ERR("Is something else using the flash controller?\n");
+ FL_ERR(" Are you reading erased flash?\n");
+ FL_ERR(" Is something else using the flash controller?\n");
return FLASH_ERR_BAD_READ;
}
@@ -131,160 +194,125 @@ int ffs_init(uint32_t offset, uint32_t max_size, struct blocklevel_device *bl,
f->bl = bl;
/* Convert and check flash header */
- rc = ffs_check_convert_header(&f->hdr, &hdr);
+ rc = ffs_check_convert_header(&f->hdr, &raw_hdr);
if (rc) {
FL_ERR("FFS: Error %d checking flash header\n", rc);
goto out;
}
/*
- * Decide how much of the image to grab to get the whole
- * partition map.
+ * Grab the entire partition header
*/
- f->cached_size = f->hdr.block_size * f->hdr.size;
- FL_DBG("FFS: Partition map size: 0x%x\n", f->cached_size);
+ FL_DBG("FFS: Partition map size: 0x%x\n", f->hdr.size);
+ if (offset + f->hdr.size > max_size) {
+ FL_ERR("FFS: Partition table is bigger than max_size\n");
+ goto out;
+ }
/* Allocate cache */
- f->cache = malloc(f->cached_size);
+ f->cache = malloc(f->hdr.size);
if (!f->cache) {
rc = FLASH_ERR_MALLOC_FAILED;
goto out;
}
/* Read the cached map */
- rc = blocklevel_read(bl, offset, f->cache, f->cached_size);
+ rc = blocklevel_read(bl, offset, f->cache, f->hdr.size);
if (rc) {
FL_ERR("FFS: Error %d reading flash partition map\n", rc);
goto out;
}
- if (mark_ecc) {
- uint32_t start, total_size;
- bool ecc;
- for (i = 0; i < f->hdr.entry_count; i++) {
- rc = ffs_part_info(f, i, NULL, &start, &total_size,
- NULL, &ecc);
+ list_head_init(&f->hdr.entries);
+ for (i = 0; i < be32_to_cpu(raw_hdr.entry_count); i++) {
+ ent = calloc(1, sizeof(struct ffs_entry));
+ if (!ent) {
+ rc = FLASH_ERR_MALLOC_FAILED;
+ goto out;
+ }
+
+ list_add_tail(&f->hdr.entries, &ent->list);
+ rc = ffs_entry_to_cpu(&f->hdr, ent, &f->cache->entries[i]);
+ if (rc)
+ goto out;
+
+ if (mark_ecc && has_ecc(ent)) {
+ rc = blocklevel_ecc_protect(bl, ent->base, ent->size);
if (rc) {
- FL_ERR("FFS: Failed to read ffs partition %d\n",
- i);
+ FL_ERR("Failed to blocklevel_ecc_protect(0x%08x, 0x%08x)\n",
+ ent->base, ent->size);
goto out;
}
- if (ecc) {
- rc = blocklevel_ecc_protect(bl, start, total_size);
- if (rc) {
- FL_ERR("Failed to blocklevel_ecc_protect(0x%08x, 0x%08x)\n",
- start, total_size);
- goto out;
- }
- } /* ecc */
- } /* for */
+ }
}
out:
if (rc == 0)
*ffs = f;
else
- free(f);
+ ffs_close(f);
return rc;
}
void ffs_close(struct ffs_handle *ffs)
{
- if (ffs->cache)
- free(ffs->cache);
- free(ffs);
-}
+ struct ffs_entry *ent, *next;
-static struct ffs_entry *ffs_get_part(struct ffs_handle *ffs, uint32_t index,
- uint32_t *out_offset)
-{
- uint32_t esize = ffs->hdr.entry_size;
- uint32_t offset = FFS_HDR_SIZE + index * esize;
-
- if (index > ffs->hdr.entry_count)
- return NULL;
- if (out_offset)
- *out_offset = ffs->toc_offset + offset;
- return (struct ffs_entry *)(ffs->cache + offset);
-}
+ list_for_each_safe(&ffs->hdr.entries, ent, next, list) {
+ list_del(&ent->list);
+ free(ent);
+ }
-static int ffs_check_convert_entry(struct ffs_entry *dst, struct ffs_entry *src)
-{
- if (ffs_checksum(src, FFS_ENTRY_SIZE) != 0)
- return FFS_ERR_BAD_CKSUM;
- memcpy(dst->name, src->name, sizeof(dst->name));
- dst->base = be32_to_cpu(src->base);
- dst->size = be32_to_cpu(src->size);
- dst->pid = be32_to_cpu(src->pid);
- dst->id = be32_to_cpu(src->id);
- dst->type = be32_to_cpu(src->type);
- dst->flags = be32_to_cpu(src->flags);
- dst->actual = be32_to_cpu(src->actual);
- dst->user.datainteg = be16_to_cpu(src->user.datainteg);
+ if (ffs->cache)
+ free(ffs->cache);
- return 0;
+ free(ffs);
}
int ffs_lookup_part(struct ffs_handle *ffs, const char *name,
uint32_t *part_idx)
{
- struct ffs_entry ent;
- uint32_t i;
- int rc;
+ int i = 0;
+ struct ffs_entry *ent;
- /* Lookup the requested partition */
- for (i = 0; i < ffs->hdr.entry_count; i++) {
- struct ffs_entry *src_ent = ffs_get_part(ffs, i, NULL);
- rc = ffs_check_convert_entry(&ent, src_ent);
- if (rc) {
- FL_ERR("FFS: Bad entry %d in partition map\n", i);
- continue;
- }
- if (!strncmp(name, ent.name, sizeof(ent.name)))
+ list_for_each(&ffs->hdr.entries, ent, list) {
+ if (!strncmp(name, ent->name, sizeof(ent->name)))
break;
+ i++;
}
- if (i >= ffs->hdr.entry_count)
- return FFS_ERR_PART_NOT_FOUND;
+
if (part_idx)
*part_idx = i;
- return 0;
+ return ent ? 0 : FFS_ERR_PART_NOT_FOUND;
}
int ffs_part_info(struct ffs_handle *ffs, uint32_t part_idx,
char **name, uint32_t *start,
uint32_t *total_size, uint32_t *act_size, bool *ecc)
{
- struct ffs_entry *raw_ent;
- struct ffs_entry ent;
+ struct ffs_entry *ent;
char *n;
- int rc;
-
- if (part_idx >= ffs->hdr.entry_count)
- return FFS_ERR_PART_NOT_FOUND;
- raw_ent = ffs_get_part(ffs, part_idx, NULL);
- if (!raw_ent)
+ ent = ffs_get_part(ffs, part_idx);
+ if (!ent)
return FFS_ERR_PART_NOT_FOUND;
- rc = ffs_check_convert_entry(&ent, raw_ent);
- if (rc) {
- FL_ERR("FFS: Bad entry %d in partition map\n", part_idx);
- return rc;
- }
if (start)
- *start = ent.base * ffs->hdr.block_size;
+ *start = ent->base;
if (total_size)
- *total_size = ent.size * ffs->hdr.block_size;
+ *total_size = ent->size;
if (act_size)
- *act_size = ent.actual;
+ *act_size = ent->actual;
if (ecc)
- *ecc = ((ent.user.datainteg & FFS_ENRY_INTEG_ECC) != 0);
+ *ecc = has_ecc(ent);
if (name) {
n = malloc(PART_NAME_MAX + 1);
+ if (!n)
+ return FLASH_ERR_MALLOC_FAILED;
memset(n, 0, PART_NAME_MAX + 1);
- strncpy(n, ent.name, PART_NAME_MAX);
+ strncpy(n, ent->name, PART_NAME_MAX);
*name = n;
}
return 0;
@@ -334,33 +362,30 @@ int ffs_update_act_size(struct ffs_handle *ffs, uint32_t part_idx,
uint32_t act_size)
{
struct ffs_entry *ent;
+ struct __ffs_entry raw_ent;
uint32_t offset;
+ int rc;
- if (part_idx >= ffs->hdr.entry_count) {
- FL_DBG("FFS: Entry out of bound\n");
- return FFS_ERR_PART_NOT_FOUND;
- }
-
- ent = ffs_get_part(ffs, part_idx, &offset);
+ ent = ffs_get_part(ffs, part_idx);
if (!ent) {
FL_DBG("FFS: Entry not found\n");
return FFS_ERR_PART_NOT_FOUND;
}
+ offset = ent->base;
FL_DBG("FFS: part index %d at offset 0x%08x\n",
part_idx, offset);
- /*
- * NOTE: We are accessing the unconverted ffs_entry from the PNOR here
- * (since we are going to write it back) so we need to be endian safe.
- */
- if (ent->actual == cpu_to_be32(act_size)) {
+ if (ent->actual == act_size) {
FL_DBG("FFS: ent->actual alrady matches: 0x%08x==0x%08x\n",
- cpu_to_be32(act_size), ent->actual);
+ act_size, ent->actual);
return 0;
}
- ent->actual = cpu_to_be32(act_size);
- ent->checksum = ffs_checksum(ent, FFS_ENTRY_SIZE_CSUM);
+ ent->actual = act_size;
+
+ rc = ffs_entry_to_flash(&ffs->hdr, &raw_ent, ent);
+ if (rc)
+ return rc;
- return blocklevel_write(ffs->bl, offset, ent, FFS_ENTRY_SIZE);
+ return blocklevel_write(ffs->bl, offset, &raw_ent, sizeof(struct __ffs_entry));
}
diff --git a/libflash/libffs.h b/libflash/libffs.h
index a9ff574..23ccb8f 100644
--- a/libflash/libffs.h
+++ b/libflash/libffs.h
@@ -22,6 +22,7 @@
/* FFS handle, opaque */
struct ffs_handle;
+struct ffs_entry;
/* Error codes:
*
@@ -34,6 +35,16 @@ struct ffs_handle;
#define FFS_ERR_BAD_CKSUM 102
#define FFS_ERR_PART_NOT_FOUND 103
#define FFS_ERR_BAD_ECC 104
+#define FFS_ERR_BAD_SIZE 105
+#define FFS_ERR_BAD_PART_NAME 106
+#define FFS_ERR_BAD_PART_BASE 107
+#define FFS_ERR_BAD_PART_SIZE 108
+#define FFS_ERR_BAD_PART_PID 109
+
+static inline bool has_ecc(struct ffs_entry *ent)
+{
+ return ((ent->user.datainteg & FFS_ENRY_INTEG_ECC) != 0);
+}
/* Init */
--
2.7.3
More information about the Skiboot
mailing list