mirror of
https://github.com/torvalds/linux.git
synced 2024-11-22 20:22:09 +00:00
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 <miquel.raynal@bootlin.com> Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org> Link: https://lore.kernel.org/r/20230404172148.82422-24-srinivas.kandagatla@linaro.org Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
fe191489d6
commit
d3c0d12f64
@ -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
|
||||
|
@ -4,3 +4,4 @@
|
||||
#
|
||||
|
||||
obj-$(CONFIG_NVMEM_LAYOUT_SL28_VPD) += sl28vpd.o
|
||||
obj-$(CONFIG_NVMEM_LAYOUT_ONIE_TLV) += onie-tlv.o
|
||||
|
257
drivers/nvmem/layouts/onie-tlv.c
Normal file
257
drivers/nvmem/layouts/onie-tlv.c
Normal file
@ -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 <miquel.raynal@bootlin.com>
|
||||
* Based on the nvmem driver written by: Vadym Kochan <vadym.kochan@plvision.eu>
|
||||
* Inspired by the first layout written by: Rafał Miłecki <rafal@milecki.pl>
|
||||
*/
|
||||
|
||||
#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 <miquel.raynal@bootlin.com>");
|
||||
MODULE_DESCRIPTION("NVMEM layout driver for Onie TLV table parsing");
|
||||
MODULE_ALIAS("NVMEM layout driver for Onie TLV table parsing");
|
Loading…
Reference in New Issue
Block a user