brintos

brintos / linux-shallow public Read only

0
0
Text · 5.7 KiB · 0967a32 Raw
261 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * ONIE tlv NVMEM cells provider4 *5 * Copyright (C) 2022 Open Compute Group ONIE6 * Author: Miquel Raynal <miquel.raynal@bootlin.com>7 * Based on the nvmem driver written by: Vadym Kochan <vadym.kochan@plvision.eu>8 * Inspired by the first layout written by: Rafał Miłecki <rafal@milecki.pl>9 */10 11#include <linux/crc32.h>12#include <linux/etherdevice.h>13#include <linux/nvmem-consumer.h>14#include <linux/nvmem-provider.h>15#include <linux/of.h>16 17#define ONIE_TLV_MAX_LEN 204818#define ONIE_TLV_CRC_FIELD_SZ 619#define ONIE_TLV_CRC_SZ 420#define ONIE_TLV_HDR_ID	"TlvInfo"21 22struct onie_tlv_hdr {23	u8 id[8];24	u8 version;25	__be16 data_len;26} __packed;27 28struct onie_tlv {29	u8 type;30	u8 len;31} __packed;32 33static const char *onie_tlv_cell_name(u8 type)34{35	switch (type) {36	case 0x21:37		return "product-name";38	case 0x22:39		return "part-number";40	case 0x23:41		return "serial-number";42	case 0x24:43		return "mac-address";44	case 0x25:45		return "manufacture-date";46	case 0x26:47		return "device-version";48	case 0x27:49		return "label-revision";50	case 0x28:51		return "platform-name";52	case 0x29:53		return "onie-version";54	case 0x2A:55		return "num-macs";56	case 0x2B:57		return "manufacturer";58	case 0x2C:59		return "country-code";60	case 0x2D:61		return "vendor";62	case 0x2E:63		return "diag-version";64	case 0x2F:65		return "service-tag";66	case 0xFD:67		return "vendor-extension";68	case 0xFE:69		return "crc32";70	default:71		break;72	}73 74	return NULL;75}76 77static int onie_tlv_mac_read_cb(void *priv, const char *id, int index,78				unsigned int offset, void *buf,79				size_t bytes)80{81	eth_addr_add(buf, index);82 83	return 0;84}85 86static nvmem_cell_post_process_t onie_tlv_read_cb(u8 type, u8 *buf)87{88	switch (type) {89	case 0x24:90		return &onie_tlv_mac_read_cb;91	default:92		break;93	}94 95	return NULL;96}97 98static int onie_tlv_add_cells(struct device *dev, struct nvmem_device *nvmem,99			      size_t data_len, u8 *data)100{101	struct nvmem_cell_info cell = {};102	struct device_node *layout;103	struct onie_tlv tlv;104	unsigned int hdr_len = sizeof(struct onie_tlv_hdr);105	unsigned int offset = 0;106	int ret;107 108	layout = of_nvmem_layout_get_container(nvmem);109	if (!layout)110		return -ENOENT;111 112	while (offset < data_len) {113		memcpy(&tlv, data + offset, sizeof(tlv));114		if (offset + tlv.len >= data_len) {115			dev_err(dev, "Out of bounds field (0x%x bytes at 0x%x)\n",116				tlv.len, hdr_len + offset);117			break;118		}119 120		cell.name = onie_tlv_cell_name(tlv.type);121		if (!cell.name)122			continue;123 124		cell.offset = hdr_len + offset + sizeof(tlv.type) + sizeof(tlv.len);125		cell.bytes = tlv.len;126		cell.np = of_get_child_by_name(layout, cell.name);127		cell.read_post_process = onie_tlv_read_cb(tlv.type, data + offset + sizeof(tlv));128 129		ret = nvmem_add_one_cell(nvmem, &cell);130		if (ret) {131			of_node_put(layout);132			return ret;133		}134 135		offset += sizeof(tlv) + tlv.len;136	}137 138	of_node_put(layout);139 140	return 0;141}142 143static bool onie_tlv_hdr_is_valid(struct device *dev, struct onie_tlv_hdr *hdr)144{145	if (memcmp(hdr->id, ONIE_TLV_HDR_ID, sizeof(hdr->id))) {146		dev_err(dev, "Invalid header\n");147		return false;148	}149 150	if (hdr->version != 0x1) {151		dev_err(dev, "Invalid version number\n");152		return false;153	}154 155	return true;156}157 158static bool onie_tlv_crc_is_valid(struct device *dev, size_t table_len, u8 *table)159{160	struct onie_tlv crc_hdr;161	u32 read_crc, calc_crc;162	__be32 crc_be;163 164	memcpy(&crc_hdr, table + table_len - ONIE_TLV_CRC_FIELD_SZ, sizeof(crc_hdr));165	if (crc_hdr.type != 0xfe || crc_hdr.len != ONIE_TLV_CRC_SZ) {166		dev_err(dev, "Invalid CRC field\n");167		return false;168	}169 170	/* The table contains a JAMCRC, which is XOR'ed compared to the original171	 * CRC32 implementation as known in the Ethernet world.172	 */173	memcpy(&crc_be, table + table_len - ONIE_TLV_CRC_SZ, ONIE_TLV_CRC_SZ);174	read_crc = be32_to_cpu(crc_be);175	calc_crc = crc32(~0, table, table_len - ONIE_TLV_CRC_SZ) ^ 0xFFFFFFFF;176	if (read_crc != calc_crc) {177		dev_err(dev, "Invalid CRC read: 0x%08x, expected: 0x%08x\n",178			read_crc, calc_crc);179		return false;180	}181 182	return true;183}184 185static int onie_tlv_parse_table(struct nvmem_layout *layout)186{187	struct nvmem_device *nvmem = layout->nvmem;188	struct device *dev = &layout->dev;189	struct onie_tlv_hdr hdr;190	size_t table_len, data_len, hdr_len;191	u8 *table, *data;192	int ret;193 194	ret = nvmem_device_read(nvmem, 0, sizeof(hdr), &hdr);195	if (ret < 0)196		return ret;197 198	if (!onie_tlv_hdr_is_valid(dev, &hdr)) {199		dev_err(dev, "Invalid ONIE TLV header\n");200		return -EINVAL;201	}202 203	hdr_len = sizeof(hdr.id) + sizeof(hdr.version) + sizeof(hdr.data_len);204	data_len = be16_to_cpu(hdr.data_len);205	table_len = hdr_len + data_len;206	if (table_len > ONIE_TLV_MAX_LEN) {207		dev_err(dev, "Invalid ONIE TLV data length\n");208		return -EINVAL;209	}210 211	table = devm_kmalloc(dev, table_len, GFP_KERNEL);212	if (!table)213		return -ENOMEM;214 215	ret = nvmem_device_read(nvmem, 0, table_len, table);216	if (ret != table_len)217		return ret;218 219	if (!onie_tlv_crc_is_valid(dev, table_len, table))220		return -EINVAL;221 222	data = table + hdr_len;223	ret = onie_tlv_add_cells(dev, nvmem, data_len, data);224	if (ret)225		return ret;226 227	return 0;228}229 230static int onie_tlv_probe(struct nvmem_layout *layout)231{232	layout->add_cells = onie_tlv_parse_table;233 234	return nvmem_layout_register(layout);235}236 237static void onie_tlv_remove(struct nvmem_layout *layout)238{239	nvmem_layout_unregister(layout);240}241 242static const struct of_device_id onie_tlv_of_match_table[] = {243	{ .compatible = "onie,tlv-layout", },244	{},245};246MODULE_DEVICE_TABLE(of, onie_tlv_of_match_table);247 248static struct nvmem_layout_driver onie_tlv_layout = {249	.driver = {250		.name = "onie-tlv-layout",251		.of_match_table = onie_tlv_of_match_table,252	},253	.probe = onie_tlv_probe,254	.remove = onie_tlv_remove,255};256module_nvmem_layout_driver(onie_tlv_layout);257 258MODULE_LICENSE("GPL");259MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>");260MODULE_DESCRIPTION("NVMEM layout driver for Onie TLV table parsing");261