811-v6.4-0011-nvmem-layouts-onie-tlv-Add-new-layout-driver.patch 7.7 KB

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