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