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)185*276dae17SMiquel Raynal static int onie_tlv_parse_table(struct device *dev, struct nvmem_device *nvmem)
186d3c0d12fSMiquel Raynal {
187d3c0d12fSMiquel Raynal struct onie_tlv_hdr hdr;
188d3c0d12fSMiquel Raynal size_t table_len, data_len, hdr_len;
189d3c0d12fSMiquel Raynal u8 *table, *data;
190d3c0d12fSMiquel Raynal int ret;
191d3c0d12fSMiquel Raynal
192d3c0d12fSMiquel Raynal ret = nvmem_device_read(nvmem, 0, sizeof(hdr), &hdr);
193d3c0d12fSMiquel Raynal if (ret < 0)
194d3c0d12fSMiquel Raynal return ret;
195d3c0d12fSMiquel Raynal
196d3c0d12fSMiquel Raynal if (!onie_tlv_hdr_is_valid(dev, &hdr)) {
197d3c0d12fSMiquel Raynal dev_err(dev, "Invalid ONIE TLV header\n");
198d3c0d12fSMiquel Raynal return -EINVAL;
199d3c0d12fSMiquel Raynal }
200d3c0d12fSMiquel Raynal
201d3c0d12fSMiquel Raynal hdr_len = sizeof(hdr.id) + sizeof(hdr.version) + sizeof(hdr.data_len);
202d3c0d12fSMiquel Raynal data_len = be16_to_cpu(hdr.data_len);
203d3c0d12fSMiquel Raynal table_len = hdr_len + data_len;
204d3c0d12fSMiquel Raynal if (table_len > ONIE_TLV_MAX_LEN) {
205d3c0d12fSMiquel Raynal dev_err(dev, "Invalid ONIE TLV data length\n");
206d3c0d12fSMiquel Raynal return -EINVAL;
207d3c0d12fSMiquel Raynal }
208d3c0d12fSMiquel Raynal
209d3c0d12fSMiquel Raynal table = devm_kmalloc(dev, table_len, GFP_KERNEL);
210d3c0d12fSMiquel Raynal if (!table)
211d3c0d12fSMiquel Raynal return -ENOMEM;
212d3c0d12fSMiquel Raynal
213d3c0d12fSMiquel Raynal ret = nvmem_device_read(nvmem, 0, table_len, table);
214d3c0d12fSMiquel Raynal if (ret != table_len)
215d3c0d12fSMiquel Raynal return ret;
216d3c0d12fSMiquel Raynal
217d3c0d12fSMiquel Raynal if (!onie_tlv_crc_is_valid(dev, table_len, table))
218d3c0d12fSMiquel Raynal return -EINVAL;
219d3c0d12fSMiquel Raynal
220d3c0d12fSMiquel Raynal data = table + hdr_len;
221d3c0d12fSMiquel Raynal ret = onie_tlv_add_cells(dev, nvmem, data_len, data);
222d3c0d12fSMiquel Raynal if (ret)
223d3c0d12fSMiquel Raynal return ret;
224d3c0d12fSMiquel Raynal
225d3c0d12fSMiquel Raynal return 0;
226d3c0d12fSMiquel Raynal }
227d3c0d12fSMiquel Raynal
228d3c0d12fSMiquel Raynal static const struct of_device_id onie_tlv_of_match_table[] = {
229d3c0d12fSMiquel Raynal { .compatible = "onie,tlv-layout", },
230d3c0d12fSMiquel Raynal {},
231d3c0d12fSMiquel Raynal };
232d3c0d12fSMiquel Raynal MODULE_DEVICE_TABLE(of, onie_tlv_of_match_table);
233d3c0d12fSMiquel Raynal
234d3c0d12fSMiquel Raynal static struct nvmem_layout onie_tlv_layout = {
235d3c0d12fSMiquel Raynal .name = "ONIE tlv layout",
236d3c0d12fSMiquel Raynal .of_match_table = onie_tlv_of_match_table,
237d3c0d12fSMiquel Raynal .add_cells = onie_tlv_parse_table,
238d3c0d12fSMiquel Raynal };
239d119eb38SMiquel Raynal module_nvmem_layout_driver(onie_tlv_layout);
240d3c0d12fSMiquel Raynal
241d3c0d12fSMiquel Raynal MODULE_LICENSE("GPL");
242d3c0d12fSMiquel Raynal MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>");
243d3c0d12fSMiquel Raynal MODULE_DESCRIPTION("NVMEM layout driver for Onie TLV table parsing");
244