On Mon, 15 Jul 2024, at 13:54, Rafał Miłecki wrote:
> From: Rafał Miłecki <ra...@milecki.pl>
>
> diff --git a/drivers/nvmem/layouts/u-boot-env.c 
> b/drivers/nvmem/layouts/u-boot-env.c
> new file mode 100644
> index 000000000000..5217dc4a52f8
> --- /dev/null
> +++ b/drivers/nvmem/layouts/u-boot-env.c
> @@ -0,0 +1,203 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2022 - 2023 Rafał Miłecki <ra...@milecki.pl>
> + */
> +
> +#include <linux/crc32.h>
> +#include <linux/etherdevice.h>
> +#include <linux/export.h>
> +#include <linux/if_ether.h>
> +#include <linux/nvmem-consumer.h>
> +#include <linux/nvmem-provider.h>
> +#include <linux/of.h>
> +#include <linux/slab.h>
> +
> +#include "u-boot-env.h"
> +
> +struct u_boot_env_image_single {
> +     __le32 crc32;
> +     uint8_t data[];
> +} __packed;
> +
> +struct u_boot_env_image_redundant {
> +     __le32 crc32;
> +     u8 mark;
> +     uint8_t data[];
> +} __packed;
> +
> +struct u_boot_env_image_broadcom {
> +     __le32 magic;
> +     __le32 len;
> +     __le32 crc32;
> +     DECLARE_FLEX_ARRAY(uint8_t, data);
> +} __packed;
> +
> +static int u_boot_env_read_post_process_ethaddr(void *context, const 
> char *id, int index,
> +                                             unsigned int offset, void *buf, 
> size_t bytes)
> +{
> +     u8 mac[ETH_ALEN];
> +
> +     if (bytes != 3 * ETH_ALEN - 1)
> +             return -EINVAL;
> +
> +     if (!mac_pton(buf, mac))
> +             return -EINVAL;
> +
> +     if (index)
> +             eth_addr_add(mac, index);
> +
> +     ether_addr_copy(buf, mac);
> +
> +     return 0;
> +}
> +
> +static int u_boot_env_parse_cells(struct device *dev, struct 
> nvmem_device *nvmem, uint8_t *buf,
> +                               size_t data_offset, size_t data_len)
> +{
> +     char *data = buf + data_offset;
> +     char *var, *value, *eq;
> +
> +     for (var = data;
> +          var < data + data_len && *var;
> +          var = value + strlen(value) + 1) {
> +             struct nvmem_cell_info info = {};
> +
> +             eq = strchr(var, '=');
> +             if (!eq)
> +                     break;
> +             *eq = '\0';
> +             value = eq + 1;
> +
> +             info.name = devm_kstrdup(dev, var, GFP_KERNEL);
> +             if (!info.name)
> +                     return -ENOMEM;
> +             info.offset = data_offset + value - data;
> +             info.bytes = strlen(value);
> +             info.np = of_get_child_by_name(dev->of_node, info.name);
> +             if (!strcmp(var, "ethaddr")) {
> +                     info.raw_len = strlen(value);
> +                     info.bytes = ETH_ALEN;
> +                     info.read_post_process = 
> u_boot_env_read_post_process_ethaddr;
> +             }
> +
> +             nvmem_add_one_cell(nvmem, &info);
> +     }
> +
> +     return 0;
> +}
> +
> +int u_boot_env_parse(struct device *dev, struct nvmem_device *nvmem,
> +                  enum u_boot_env_format format)
> +{
> +     size_t crc32_data_offset;
> +     size_t crc32_data_len;
> +     size_t crc32_offset;
> +     __le32 *crc32_addr;
> +     size_t data_offset;
> +     size_t data_len;
> +     size_t dev_size;
> +     uint32_t crc32;
> +     uint32_t calc;
> +     uint8_t *buf;
> +     int bytes;
> +     int err;
> +
> +     dev_size = nvmem_dev_size(nvmem);
> +
> +     buf = kzalloc(dev_size, GFP_KERNEL);
> +     if (!buf) {
> +             err = -ENOMEM;
> +             goto err_out;
> +     }
> +
> +     bytes = nvmem_device_read(nvmem, 0, dev_size, buf);
> +     if (bytes < 0) {
> +             err = bytes;
> +             goto err_kfree;
> +     } else if (bytes != dev_size) {
> +             err = -EIO;
> +             goto err_kfree;
> +     }
> +
> +     switch (format) {
> +     case U_BOOT_FORMAT_SINGLE:
> +             crc32_offset = offsetof(struct u_boot_env_image_single, crc32);
> +             crc32_data_offset = offsetof(struct u_boot_env_image_single, 
> data);
> +             data_offset = offsetof(struct u_boot_env_image_single, data);
> +             break;
> +     case U_BOOT_FORMAT_REDUNDANT:
> +             crc32_offset = offsetof(struct u_boot_env_image_redundant, 
> crc32);
> +             crc32_data_offset = offsetof(struct u_boot_env_image_redundant, 
> data);
> +             data_offset = offsetof(struct u_boot_env_image_redundant, data);
> +             break;
> +     case U_BOOT_FORMAT_BROADCOM:
> +             crc32_offset = offsetof(struct u_boot_env_image_broadcom, 
> crc32);
> +             crc32_data_offset = offsetof(struct u_boot_env_image_broadcom, 
> data);
> +             data_offset = offsetof(struct u_boot_env_image_broadcom, data);
> +             break;
> +     }

Could we error somewhere if bytes or dev_size is < crc32_data_offset
or alternatively if bytes is zero (above, beside bytes != dev_size)?
Detailed here: 
https://lore.kernel.org/lkml/20240612031510.14414-1-...@johnthomson.fastmail.com.au/
mtd partitioning can deliver a zero length partition (if misconfigured: Example 
u-boot partition starts beyond hardware NOR length).

> +     crc32_addr = (__le32 *)(buf + crc32_offset);
> +     crc32 = le32_to_cpu(*crc32_addr);
> +     crc32_data_len = dev_size - crc32_data_offset;
> +     data_len = dev_size - data_offset;
> +
> +     calc = crc32(~0, buf + crc32_data_offset, crc32_data_len) ^ ~0L;
> +     if (calc != crc32) {
> +             dev_err(dev, "Invalid calculated CRC32: 0x%08x (expected: 
> 0x%08x)\n", calc, crc32);
> +             err = -EINVAL;
> +             goto err_kfree;
> +     }
> +
> +     buf[dev_size - 1] = '\0';
> +     err = u_boot_env_parse_cells(dev, nvmem, buf, data_offset, data_len);
> +
> +err_kfree:
> +     kfree(buf);
> +err_out:
> +     return err;
> +}
> +EXPORT_SYMBOL_GPL(u_boot_env_parse);
> +
> +static int u_boot_env_add_cells(struct nvmem_layout *layout)
> +{
> +     struct device *dev = &layout->dev;
> +     enum u_boot_env_format format;
> +
> +     format = (uintptr_t)device_get_match_data(dev);
> +
> +     return u_boot_env_parse(dev, layout->nvmem, format);
> +}
> +
> +static int u_boot_env_probe(struct nvmem_layout *layout)
> +{
> +     layout->add_cells = u_boot_env_add_cells;
> +
> +     return nvmem_layout_register(layout);
> +}
> +
> +static void u_boot_env_remove(struct nvmem_layout *layout)
> +{
> +     nvmem_layout_unregister(layout);
> +}
> +
> +static const struct of_device_id u_boot_env_of_match_table[] = {
> +     { .compatible = "u-boot,env", .data = (void *)U_BOOT_FORMAT_SINGLE, },
> +     { .compatible = "u-boot,env-redundant-bool", .data = (void 
> *)U_BOOT_FORMAT_REDUNDANT, },
> +     { .compatible = "u-boot,env-redundant-count", .data = (void 
> *)U_BOOT_FORMAT_REDUNDANT, },
> +     { .compatible = "brcm,env", .data = (void *)U_BOOT_FORMAT_BROADCOM, },
> +     {},
> +};
> +
> +static struct nvmem_layout_driver u_boot_env_layout = {
> +     .driver = {
> +             .name = "u-boot-env-layout",
> +             .of_match_table = u_boot_env_of_match_table,
> +     },
> +     .probe = u_boot_env_probe,
> +     .remove = u_boot_env_remove,
> +};
> +module_nvmem_layout_driver(u_boot_env_layout);
> +
> +MODULE_AUTHOR("Rafał Miłecki");
> +MODULE_LICENSE("GPL");
> +MODULE_DEVICE_TABLE(of, u_boot_env_of_match_table);


-- 
  John Thomson

Reply via email to