xref: /linux/drivers/nvmem/layouts/onie-tlv.c (revision 5e3992fe72748ed3892be876f09d4d990548b7af)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * ONIE tlv NVMEM cells provider
4  *
5  * Copyright (C) 2022 Open Compute Group ONIE
6  * 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 2048
18 #define ONIE_TLV_CRC_FIELD_SZ 6
19 #define ONIE_TLV_CRC_SZ 4
20 #define ONIE_TLV_HDR_ID	"TlvInfo"
21 
22 struct onie_tlv_hdr {
23 	u8 id[8];
24 	u8 version;
25 	__be16 data_len;
26 } __packed;
27 
28 struct onie_tlv {
29 	u8 type;
30 	u8 len;
31 } __packed;
32 
33 static 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 
77 static 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 
86 static 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 
98 static 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 
143 static 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 
158 static 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 original
171 	 * 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 
185 static int onie_tlv_parse_table(struct device *dev, struct nvmem_device *nvmem,
186 				struct nvmem_layout *layout)
187 {
188 	struct onie_tlv_hdr hdr;
189 	size_t table_len, data_len, hdr_len;
190 	u8 *table, *data;
191 	int ret;
192 
193 	ret = nvmem_device_read(nvmem, 0, sizeof(hdr), &hdr);
194 	if (ret < 0)
195 		return ret;
196 
197 	if (!onie_tlv_hdr_is_valid(dev, &hdr)) {
198 		dev_err(dev, "Invalid ONIE TLV header\n");
199 		return -EINVAL;
200 	}
201 
202 	hdr_len = sizeof(hdr.id) + sizeof(hdr.version) + sizeof(hdr.data_len);
203 	data_len = be16_to_cpu(hdr.data_len);
204 	table_len = hdr_len + data_len;
205 	if (table_len > ONIE_TLV_MAX_LEN) {
206 		dev_err(dev, "Invalid ONIE TLV data length\n");
207 		return -EINVAL;
208 	}
209 
210 	table = devm_kmalloc(dev, table_len, GFP_KERNEL);
211 	if (!table)
212 		return -ENOMEM;
213 
214 	ret = nvmem_device_read(nvmem, 0, table_len, table);
215 	if (ret != table_len)
216 		return ret;
217 
218 	if (!onie_tlv_crc_is_valid(dev, table_len, table))
219 		return -EINVAL;
220 
221 	data = table + hdr_len;
222 	ret = onie_tlv_add_cells(dev, nvmem, data_len, data);
223 	if (ret)
224 		return ret;
225 
226 	return 0;
227 }
228 
229 static const struct of_device_id onie_tlv_of_match_table[] = {
230 	{ .compatible = "onie,tlv-layout", },
231 	{},
232 };
233 MODULE_DEVICE_TABLE(of, onie_tlv_of_match_table);
234 
235 static struct nvmem_layout onie_tlv_layout = {
236 	.name = "ONIE tlv layout",
237 	.of_match_table = onie_tlv_of_match_table,
238 	.add_cells = onie_tlv_parse_table,
239 };
240 module_nvmem_layout_driver(onie_tlv_layout);
241 
242 MODULE_LICENSE("GPL");
243 MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>");
244 MODULE_DESCRIPTION("NVMEM layout driver for Onie TLV table parsing");
245