kernel: backport NVMEM patches queued for the v6.4
[openwrt/staging/dedeckeh.git] / target / linux / generic / backport-5.15 / 811-v6.4-0011-nvmem-layouts-onie-tlv-Add-new-layout-driver.patch
1 From d3c0d12f6474216bf386101e2449cc73e5c5b61d Mon Sep 17 00:00:00 2001
2 From: Miquel Raynal <miquel.raynal@bootlin.com>
3 Date: Tue, 4 Apr 2023 18:21:31 +0100
4 Subject: [PATCH] nvmem: layouts: onie-tlv: Add new layout driver
5
6 This layout applies on top of any non volatile storage device containing
7 an ONIE table factory flashed. This table follows the tlv
8 (type-length-value) organization described in the link below. We cannot
9 afford using regular parsers because the content of these tables is
10 manufacturer specific and must be dynamically discovered.
11
12 Link: https://opencomputeproject.github.io/onie/design-spec/hw_requirements.html
13 Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
14 Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
15 Link: https://lore.kernel.org/r/20230404172148.82422-24-srinivas.kandagatla@linaro.org
16 Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
17 ---
18 drivers/nvmem/layouts/Kconfig | 9 ++
19 drivers/nvmem/layouts/Makefile | 1 +
20 drivers/nvmem/layouts/onie-tlv.c | 257 +++++++++++++++++++++++++++++++
21 3 files changed, 267 insertions(+)
22 create mode 100644 drivers/nvmem/layouts/onie-tlv.c
23
24 --- a/drivers/nvmem/layouts/Kconfig
25 +++ b/drivers/nvmem/layouts/Kconfig
26 @@ -11,4 +11,13 @@ config NVMEM_LAYOUT_SL28_VPD
27
28 If unsure, say N.
29
30 +config NVMEM_LAYOUT_ONIE_TLV
31 + tristate "ONIE tlv support"
32 + select CRC32
33 + help
34 + Say Y here if you want to support the Open Compute Project ONIE
35 + Type-Length-Value standard table.
36 +
37 + If unsure, say N.
38 +
39 endmenu
40 --- a/drivers/nvmem/layouts/Makefile
41 +++ b/drivers/nvmem/layouts/Makefile
42 @@ -4,3 +4,4 @@
43 #
44
45 obj-$(CONFIG_NVMEM_LAYOUT_SL28_VPD) += sl28vpd.o
46 +obj-$(CONFIG_NVMEM_LAYOUT_ONIE_TLV) += onie-tlv.o
47 --- /dev/null
48 +++ b/drivers/nvmem/layouts/onie-tlv.c
49 @@ -0,0 +1,257 @@
50 +// SPDX-License-Identifier: GPL-2.0-only
51 +/*
52 + * ONIE tlv NVMEM cells provider
53 + *
54 + * Copyright (C) 2022 Open Compute Group ONIE
55 + * Author: Miquel Raynal <miquel.raynal@bootlin.com>
56 + * Based on the nvmem driver written by: Vadym Kochan <vadym.kochan@plvision.eu>
57 + * Inspired by the first layout written by: Rafał Miłecki <rafal@milecki.pl>
58 + */
59 +
60 +#include <linux/crc32.h>
61 +#include <linux/etherdevice.h>
62 +#include <linux/nvmem-consumer.h>
63 +#include <linux/nvmem-provider.h>
64 +#include <linux/of.h>
65 +
66 +#define ONIE_TLV_MAX_LEN 2048
67 +#define ONIE_TLV_CRC_FIELD_SZ 6
68 +#define ONIE_TLV_CRC_SZ 4
69 +#define ONIE_TLV_HDR_ID "TlvInfo"
70 +
71 +struct onie_tlv_hdr {
72 + u8 id[8];
73 + u8 version;
74 + __be16 data_len;
75 +} __packed;
76 +
77 +struct onie_tlv {
78 + u8 type;
79 + u8 len;
80 +} __packed;
81 +
82 +static const char *onie_tlv_cell_name(u8 type)
83 +{
84 + switch (type) {
85 + case 0x21:
86 + return "product-name";
87 + case 0x22:
88 + return "part-number";
89 + case 0x23:
90 + return "serial-number";
91 + case 0x24:
92 + return "mac-address";
93 + case 0x25:
94 + return "manufacture-date";
95 + case 0x26:
96 + return "device-version";
97 + case 0x27:
98 + return "label-revision";
99 + case 0x28:
100 + return "platform-name";
101 + case 0x29:
102 + return "onie-version";
103 + case 0x2A:
104 + return "num-macs";
105 + case 0x2B:
106 + return "manufacturer";
107 + case 0x2C:
108 + return "country-code";
109 + case 0x2D:
110 + return "vendor";
111 + case 0x2E:
112 + return "diag-version";
113 + case 0x2F:
114 + return "service-tag";
115 + case 0xFD:
116 + return "vendor-extension";
117 + case 0xFE:
118 + return "crc32";
119 + default:
120 + break;
121 + }
122 +
123 + return NULL;
124 +}
125 +
126 +static int onie_tlv_mac_read_cb(void *priv, const char *id, int index,
127 + unsigned int offset, void *buf,
128 + size_t bytes)
129 +{
130 + eth_addr_add(buf, index);
131 +
132 + return 0;
133 +}
134 +
135 +static nvmem_cell_post_process_t onie_tlv_read_cb(u8 type, u8 *buf)
136 +{
137 + switch (type) {
138 + case 0x24:
139 + return &onie_tlv_mac_read_cb;
140 + default:
141 + break;
142 + }
143 +
144 + return NULL;
145 +}
146 +
147 +static int onie_tlv_add_cells(struct device *dev, struct nvmem_device *nvmem,
148 + size_t data_len, u8 *data)
149 +{
150 + struct nvmem_cell_info cell = {};
151 + struct device_node *layout;
152 + struct onie_tlv tlv;
153 + unsigned int hdr_len = sizeof(struct onie_tlv_hdr);
154 + unsigned int offset = 0;
155 + int ret;
156 +
157 + layout = of_nvmem_layout_get_container(nvmem);
158 + if (!layout)
159 + return -ENOENT;
160 +
161 + while (offset < data_len) {
162 + memcpy(&tlv, data + offset, sizeof(tlv));
163 + if (offset + tlv.len >= data_len) {
164 + dev_err(dev, "Out of bounds field (0x%x bytes at 0x%x)\n",
165 + tlv.len, hdr_len + offset);
166 + break;
167 + }
168 +
169 + cell.name = onie_tlv_cell_name(tlv.type);
170 + if (!cell.name)
171 + continue;
172 +
173 + cell.offset = hdr_len + offset + sizeof(tlv.type) + sizeof(tlv.len);
174 + cell.bytes = tlv.len;
175 + cell.np = of_get_child_by_name(layout, cell.name);
176 + cell.read_post_process = onie_tlv_read_cb(tlv.type, data + offset + sizeof(tlv));
177 +
178 + ret = nvmem_add_one_cell(nvmem, &cell);
179 + if (ret) {
180 + of_node_put(layout);
181 + return ret;
182 + }
183 +
184 + offset += sizeof(tlv) + tlv.len;
185 + }
186 +
187 + of_node_put(layout);
188 +
189 + return 0;
190 +}
191 +
192 +static bool onie_tlv_hdr_is_valid(struct device *dev, struct onie_tlv_hdr *hdr)
193 +{
194 + if (memcmp(hdr->id, ONIE_TLV_HDR_ID, sizeof(hdr->id))) {
195 + dev_err(dev, "Invalid header\n");
196 + return false;
197 + }
198 +
199 + if (hdr->version != 0x1) {
200 + dev_err(dev, "Invalid version number\n");
201 + return false;
202 + }
203 +
204 + return true;
205 +}
206 +
207 +static bool onie_tlv_crc_is_valid(struct device *dev, size_t table_len, u8 *table)
208 +{
209 + struct onie_tlv crc_hdr;
210 + u32 read_crc, calc_crc;
211 + __be32 crc_be;
212 +
213 + memcpy(&crc_hdr, table + table_len - ONIE_TLV_CRC_FIELD_SZ, sizeof(crc_hdr));
214 + if (crc_hdr.type != 0xfe || crc_hdr.len != ONIE_TLV_CRC_SZ) {
215 + dev_err(dev, "Invalid CRC field\n");
216 + return false;
217 + }
218 +
219 + /* The table contains a JAMCRC, which is XOR'ed compared to the original
220 + * CRC32 implementation as known in the Ethernet world.
221 + */
222 + memcpy(&crc_be, table + table_len - ONIE_TLV_CRC_SZ, ONIE_TLV_CRC_SZ);
223 + read_crc = be32_to_cpu(crc_be);
224 + calc_crc = crc32(~0, table, table_len - ONIE_TLV_CRC_SZ) ^ 0xFFFFFFFF;
225 + if (read_crc != calc_crc) {
226 + dev_err(dev, "Invalid CRC read: 0x%08x, expected: 0x%08x\n",
227 + read_crc, calc_crc);
228 + return false;
229 + }
230 +
231 + return true;
232 +}
233 +
234 +static int onie_tlv_parse_table(struct device *dev, struct nvmem_device *nvmem,
235 + struct nvmem_layout *layout)
236 +{
237 + struct onie_tlv_hdr hdr;
238 + size_t table_len, data_len, hdr_len;
239 + u8 *table, *data;
240 + int ret;
241 +
242 + ret = nvmem_device_read(nvmem, 0, sizeof(hdr), &hdr);
243 + if (ret < 0)
244 + return ret;
245 +
246 + if (!onie_tlv_hdr_is_valid(dev, &hdr)) {
247 + dev_err(dev, "Invalid ONIE TLV header\n");
248 + return -EINVAL;
249 + }
250 +
251 + hdr_len = sizeof(hdr.id) + sizeof(hdr.version) + sizeof(hdr.data_len);
252 + data_len = be16_to_cpu(hdr.data_len);
253 + table_len = hdr_len + data_len;
254 + if (table_len > ONIE_TLV_MAX_LEN) {
255 + dev_err(dev, "Invalid ONIE TLV data length\n");
256 + return -EINVAL;
257 + }
258 +
259 + table = devm_kmalloc(dev, table_len, GFP_KERNEL);
260 + if (!table)
261 + return -ENOMEM;
262 +
263 + ret = nvmem_device_read(nvmem, 0, table_len, table);
264 + if (ret != table_len)
265 + return ret;
266 +
267 + if (!onie_tlv_crc_is_valid(dev, table_len, table))
268 + return -EINVAL;
269 +
270 + data = table + hdr_len;
271 + ret = onie_tlv_add_cells(dev, nvmem, data_len, data);
272 + if (ret)
273 + return ret;
274 +
275 + return 0;
276 +}
277 +
278 +static const struct of_device_id onie_tlv_of_match_table[] = {
279 + { .compatible = "onie,tlv-layout", },
280 + {},
281 +};
282 +MODULE_DEVICE_TABLE(of, onie_tlv_of_match_table);
283 +
284 +static struct nvmem_layout onie_tlv_layout = {
285 + .name = "ONIE tlv layout",
286 + .of_match_table = onie_tlv_of_match_table,
287 + .add_cells = onie_tlv_parse_table,
288 +};
289 +
290 +static int __init onie_tlv_init(void)
291 +{
292 + return nvmem_layout_register(&onie_tlv_layout);
293 +}
294 +
295 +static void __exit onie_tlv_exit(void)
296 +{
297 + nvmem_layout_unregister(&onie_tlv_layout);
298 +}
299 +
300 +module_init(onie_tlv_init);
301 +module_exit(onie_tlv_exit);
302 +
303 +MODULE_LICENSE("GPL");
304 +MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>");
305 +MODULE_DESCRIPTION("NVMEM layout driver for Onie TLV table parsing");
306 +MODULE_ALIAS("NVMEM layout driver for Onie TLV table parsing");