| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- From d3c0d12f6474216bf386101e2449cc73e5c5b61d Mon Sep 17 00:00:00 2001
- From: Miquel Raynal <[email protected]>
- Date: Tue, 4 Apr 2023 18:21:31 +0100
- Subject: [PATCH] nvmem: layouts: onie-tlv: Add new layout driver
- This layout applies on top of any non volatile storage device containing
- an ONIE table factory flashed. This table follows the tlv
- (type-length-value) organization described in the link below. We cannot
- afford using regular parsers because the content of these tables is
- manufacturer specific and must be dynamically discovered.
- Link: https://opencomputeproject.github.io/onie/design-spec/hw_requirements.html
- Signed-off-by: Miquel Raynal <[email protected]>
- Signed-off-by: Srinivas Kandagatla <[email protected]>
- Link: https://lore.kernel.org/r/[email protected]
- Signed-off-by: Greg Kroah-Hartman <[email protected]>
- ---
- drivers/nvmem/layouts/Kconfig | 9 ++
- drivers/nvmem/layouts/Makefile | 1 +
- drivers/nvmem/layouts/onie-tlv.c | 257 +++++++++++++++++++++++++++++++
- 3 files changed, 267 insertions(+)
- create mode 100644 drivers/nvmem/layouts/onie-tlv.c
- --- a/drivers/nvmem/layouts/Kconfig
- +++ b/drivers/nvmem/layouts/Kconfig
- @@ -11,4 +11,13 @@ config NVMEM_LAYOUT_SL28_VPD
-
- If unsure, say N.
-
- +config NVMEM_LAYOUT_ONIE_TLV
- + tristate "ONIE tlv support"
- + select CRC32
- + help
- + Say Y here if you want to support the Open Compute Project ONIE
- + Type-Length-Value standard table.
- +
- + If unsure, say N.
- +
- endmenu
- --- a/drivers/nvmem/layouts/Makefile
- +++ b/drivers/nvmem/layouts/Makefile
- @@ -4,3 +4,4 @@
- #
-
- obj-$(CONFIG_NVMEM_LAYOUT_SL28_VPD) += sl28vpd.o
- +obj-$(CONFIG_NVMEM_LAYOUT_ONIE_TLV) += onie-tlv.o
- --- /dev/null
- +++ b/drivers/nvmem/layouts/onie-tlv.c
- @@ -0,0 +1,257 @@
- +// SPDX-License-Identifier: GPL-2.0-only
- +/*
- + * ONIE tlv NVMEM cells provider
- + *
- + * Copyright (C) 2022 Open Compute Group ONIE
- + * Author: Miquel Raynal <[email protected]>
- + * Based on the nvmem driver written by: Vadym Kochan <[email protected]>
- + * Inspired by the first layout written by: Rafał Miłecki <[email protected]>
- + */
- +
- +#include <linux/crc32.h>
- +#include <linux/etherdevice.h>
- +#include <linux/nvmem-consumer.h>
- +#include <linux/nvmem-provider.h>
- +#include <linux/of.h>
- +
- +#define ONIE_TLV_MAX_LEN 2048
- +#define ONIE_TLV_CRC_FIELD_SZ 6
- +#define ONIE_TLV_CRC_SZ 4
- +#define ONIE_TLV_HDR_ID "TlvInfo"
- +
- +struct onie_tlv_hdr {
- + u8 id[8];
- + u8 version;
- + __be16 data_len;
- +} __packed;
- +
- +struct onie_tlv {
- + u8 type;
- + u8 len;
- +} __packed;
- +
- +static const char *onie_tlv_cell_name(u8 type)
- +{
- + switch (type) {
- + case 0x21:
- + return "product-name";
- + case 0x22:
- + return "part-number";
- + case 0x23:
- + return "serial-number";
- + case 0x24:
- + return "mac-address";
- + case 0x25:
- + return "manufacture-date";
- + case 0x26:
- + return "device-version";
- + case 0x27:
- + return "label-revision";
- + case 0x28:
- + return "platform-name";
- + case 0x29:
- + return "onie-version";
- + case 0x2A:
- + return "num-macs";
- + case 0x2B:
- + return "manufacturer";
- + case 0x2C:
- + return "country-code";
- + case 0x2D:
- + return "vendor";
- + case 0x2E:
- + return "diag-version";
- + case 0x2F:
- + return "service-tag";
- + case 0xFD:
- + return "vendor-extension";
- + case 0xFE:
- + return "crc32";
- + default:
- + break;
- + }
- +
- + return NULL;
- +}
- +
- +static int onie_tlv_mac_read_cb(void *priv, const char *id, int index,
- + unsigned int offset, void *buf,
- + size_t bytes)
- +{
- + eth_addr_add(buf, index);
- +
- + return 0;
- +}
- +
- +static nvmem_cell_post_process_t onie_tlv_read_cb(u8 type, u8 *buf)
- +{
- + switch (type) {
- + case 0x24:
- + return &onie_tlv_mac_read_cb;
- + default:
- + break;
- + }
- +
- + return NULL;
- +}
- +
- +static int onie_tlv_add_cells(struct device *dev, struct nvmem_device *nvmem,
- + size_t data_len, u8 *data)
- +{
- + struct nvmem_cell_info cell = {};
- + struct device_node *layout;
- + struct onie_tlv tlv;
- + unsigned int hdr_len = sizeof(struct onie_tlv_hdr);
- + unsigned int offset = 0;
- + int ret;
- +
- + layout = of_nvmem_layout_get_container(nvmem);
- + if (!layout)
- + return -ENOENT;
- +
- + while (offset < data_len) {
- + memcpy(&tlv, data + offset, sizeof(tlv));
- + if (offset + tlv.len >= data_len) {
- + dev_err(dev, "Out of bounds field (0x%x bytes at 0x%x)\n",
- + tlv.len, hdr_len + offset);
- + break;
- + }
- +
- + cell.name = onie_tlv_cell_name(tlv.type);
- + if (!cell.name)
- + continue;
- +
- + cell.offset = hdr_len + offset + sizeof(tlv.type) + sizeof(tlv.len);
- + cell.bytes = tlv.len;
- + cell.np = of_get_child_by_name(layout, cell.name);
- + cell.read_post_process = onie_tlv_read_cb(tlv.type, data + offset + sizeof(tlv));
- +
- + ret = nvmem_add_one_cell(nvmem, &cell);
- + if (ret) {
- + of_node_put(layout);
- + return ret;
- + }
- +
- + offset += sizeof(tlv) + tlv.len;
- + }
- +
- + of_node_put(layout);
- +
- + return 0;
- +}
- +
- +static bool onie_tlv_hdr_is_valid(struct device *dev, struct onie_tlv_hdr *hdr)
- +{
- + if (memcmp(hdr->id, ONIE_TLV_HDR_ID, sizeof(hdr->id))) {
- + dev_err(dev, "Invalid header\n");
- + return false;
- + }
- +
- + if (hdr->version != 0x1) {
- + dev_err(dev, "Invalid version number\n");
- + return false;
- + }
- +
- + return true;
- +}
- +
- +static bool onie_tlv_crc_is_valid(struct device *dev, size_t table_len, u8 *table)
- +{
- + struct onie_tlv crc_hdr;
- + u32 read_crc, calc_crc;
- + __be32 crc_be;
- +
- + memcpy(&crc_hdr, table + table_len - ONIE_TLV_CRC_FIELD_SZ, sizeof(crc_hdr));
- + if (crc_hdr.type != 0xfe || crc_hdr.len != ONIE_TLV_CRC_SZ) {
- + dev_err(dev, "Invalid CRC field\n");
- + return false;
- + }
- +
- + /* The table contains a JAMCRC, which is XOR'ed compared to the original
- + * CRC32 implementation as known in the Ethernet world.
- + */
- + memcpy(&crc_be, table + table_len - ONIE_TLV_CRC_SZ, ONIE_TLV_CRC_SZ);
- + read_crc = be32_to_cpu(crc_be);
- + calc_crc = crc32(~0, table, table_len - ONIE_TLV_CRC_SZ) ^ 0xFFFFFFFF;
- + if (read_crc != calc_crc) {
- + dev_err(dev, "Invalid CRC read: 0x%08x, expected: 0x%08x\n",
- + read_crc, calc_crc);
- + return false;
- + }
- +
- + return true;
- +}
- +
- +static int onie_tlv_parse_table(struct device *dev, struct nvmem_device *nvmem,
- + struct nvmem_layout *layout)
- +{
- + struct onie_tlv_hdr hdr;
- + size_t table_len, data_len, hdr_len;
- + u8 *table, *data;
- + int ret;
- +
- + ret = nvmem_device_read(nvmem, 0, sizeof(hdr), &hdr);
- + if (ret < 0)
- + return ret;
- +
- + if (!onie_tlv_hdr_is_valid(dev, &hdr)) {
- + dev_err(dev, "Invalid ONIE TLV header\n");
- + return -EINVAL;
- + }
- +
- + hdr_len = sizeof(hdr.id) + sizeof(hdr.version) + sizeof(hdr.data_len);
- + data_len = be16_to_cpu(hdr.data_len);
- + table_len = hdr_len + data_len;
- + if (table_len > ONIE_TLV_MAX_LEN) {
- + dev_err(dev, "Invalid ONIE TLV data length\n");
- + return -EINVAL;
- + }
- +
- + table = devm_kmalloc(dev, table_len, GFP_KERNEL);
- + if (!table)
- + return -ENOMEM;
- +
- + ret = nvmem_device_read(nvmem, 0, table_len, table);
- + if (ret != table_len)
- + return ret;
- +
- + if (!onie_tlv_crc_is_valid(dev, table_len, table))
- + return -EINVAL;
- +
- + data = table + hdr_len;
- + ret = onie_tlv_add_cells(dev, nvmem, data_len, data);
- + if (ret)
- + return ret;
- +
- + return 0;
- +}
- +
- +static const struct of_device_id onie_tlv_of_match_table[] = {
- + { .compatible = "onie,tlv-layout", },
- + {},
- +};
- +MODULE_DEVICE_TABLE(of, onie_tlv_of_match_table);
- +
- +static struct nvmem_layout onie_tlv_layout = {
- + .name = "ONIE tlv layout",
- + .of_match_table = onie_tlv_of_match_table,
- + .add_cells = onie_tlv_parse_table,
- +};
- +
- +static int __init onie_tlv_init(void)
- +{
- + return nvmem_layout_register(&onie_tlv_layout);
- +}
- +
- +static void __exit onie_tlv_exit(void)
- +{
- + nvmem_layout_unregister(&onie_tlv_layout);
- +}
- +
- +module_init(onie_tlv_init);
- +module_exit(onie_tlv_exit);
- +
- +MODULE_LICENSE("GPL");
- +MODULE_AUTHOR("Miquel Raynal <[email protected]>");
- +MODULE_DESCRIPTION("NVMEM layout driver for Onie TLV table parsing");
- +MODULE_ALIAS("NVMEM layout driver for Onie TLV table parsing");
|