xref: /openbmc/linux/drivers/nvmem/u-boot-env.c (revision 769218209d8f00a4003dc9ee351198a473043eaf)
1d5542923SRafał Miłecki // SPDX-License-Identifier: GPL-2.0-only
2d5542923SRafał Miłecki /*
3d5542923SRafał Miłecki  * Copyright (C) 2022 Rafał Miłecki <rafal@milecki.pl>
4d5542923SRafał Miłecki  */
5d5542923SRafał Miłecki 
6d5542923SRafał Miłecki #include <linux/crc32.h>
7c49f1a8aSRafał Miłecki #include <linux/etherdevice.h>
8c49f1a8aSRafał Miłecki #include <linux/if_ether.h>
9d5542923SRafał Miłecki #include <linux/mod_devicetable.h>
10d5542923SRafał Miłecki #include <linux/module.h>
11d5542923SRafał Miłecki #include <linux/mtd/mtd.h>
12d5542923SRafał Miłecki #include <linux/nvmem-consumer.h>
13d5542923SRafał Miłecki #include <linux/nvmem-provider.h>
149bf75da0SRob Herring #include <linux/of.h>
15d5542923SRafał Miłecki #include <linux/platform_device.h>
16d5542923SRafał Miłecki #include <linux/slab.h>
17d5542923SRafał Miłecki 
18d5542923SRafał Miłecki enum u_boot_env_format {
19d5542923SRafał Miłecki 	U_BOOT_FORMAT_SINGLE,
20d5542923SRafał Miłecki 	U_BOOT_FORMAT_REDUNDANT,
21ada84d07SRafał Miłecki 	U_BOOT_FORMAT_BROADCOM,
22d5542923SRafał Miłecki };
23d5542923SRafał Miłecki 
24d5542923SRafał Miłecki struct u_boot_env {
25d5542923SRafał Miłecki 	struct device *dev;
26ae91c9c7SRafał Miłecki 	struct nvmem_device *nvmem;
27d5542923SRafał Miłecki 	enum u_boot_env_format format;
28d5542923SRafał Miłecki 
29d5542923SRafał Miłecki 	struct mtd_info *mtd;
30d5542923SRafał Miłecki };
31d5542923SRafał Miłecki 
32d5542923SRafał Miłecki struct u_boot_env_image_single {
33d5542923SRafał Miłecki 	__le32 crc32;
34d5542923SRafał Miłecki 	uint8_t data[];
35d5542923SRafał Miłecki } __packed;
36d5542923SRafał Miłecki 
37d5542923SRafał Miłecki struct u_boot_env_image_redundant {
38d5542923SRafał Miłecki 	__le32 crc32;
39d5542923SRafał Miłecki 	u8 mark;
40d5542923SRafał Miłecki 	uint8_t data[];
41d5542923SRafał Miłecki } __packed;
42d5542923SRafał Miłecki 
43ada84d07SRafał Miłecki struct u_boot_env_image_broadcom {
44ada84d07SRafał Miłecki 	__le32 magic;
45ada84d07SRafał Miłecki 	__le32 len;
46ada84d07SRafał Miłecki 	__le32 crc32;
471006ebe9SAtul Raut 	DECLARE_FLEX_ARRAY(uint8_t, data);
48ada84d07SRafał Miłecki } __packed;
49ada84d07SRafał Miłecki 
u_boot_env_read(void * context,unsigned int offset,void * val,size_t bytes)50d5542923SRafał Miłecki static int u_boot_env_read(void *context, unsigned int offset, void *val,
51d5542923SRafał Miłecki 			   size_t bytes)
52d5542923SRafał Miłecki {
53d5542923SRafał Miłecki 	struct u_boot_env *priv = context;
54d5542923SRafał Miłecki 	struct device *dev = priv->dev;
55d5542923SRafał Miłecki 	size_t bytes_read;
56d5542923SRafał Miłecki 	int err;
57d5542923SRafał Miłecki 
58d5542923SRafał Miłecki 	err = mtd_read(priv->mtd, offset, bytes, &bytes_read, val);
59d5542923SRafał Miłecki 	if (err && !mtd_is_bitflip(err)) {
60d5542923SRafał Miłecki 		dev_err(dev, "Failed to read from mtd: %d\n", err);
61d5542923SRafał Miłecki 		return err;
62d5542923SRafał Miłecki 	}
63d5542923SRafał Miłecki 
64d5542923SRafał Miłecki 	if (bytes_read != bytes) {
65d5542923SRafał Miłecki 		dev_err(dev, "Failed to read %zu bytes\n", bytes);
66d5542923SRafał Miłecki 		return -EIO;
67d5542923SRafał Miłecki 	}
68d5542923SRafał Miłecki 
69d5542923SRafał Miłecki 	return 0;
70d5542923SRafał Miłecki }
71d5542923SRafał Miłecki 
u_boot_env_read_post_process_ethaddr(void * context,const char * id,int index,unsigned int offset,void * buf,size_t bytes)72c49f1a8aSRafał Miłecki static int u_boot_env_read_post_process_ethaddr(void *context, const char *id, int index,
73c49f1a8aSRafał Miłecki 						unsigned int offset, void *buf, size_t bytes)
74c49f1a8aSRafał Miłecki {
75c49f1a8aSRafał Miłecki 	u8 mac[ETH_ALEN];
76c49f1a8aSRafał Miłecki 
77c49f1a8aSRafał Miłecki 	if (bytes != 3 * ETH_ALEN - 1)
78c49f1a8aSRafał Miłecki 		return -EINVAL;
79c49f1a8aSRafał Miłecki 
80c49f1a8aSRafał Miłecki 	if (!mac_pton(buf, mac))
81c49f1a8aSRafał Miłecki 		return -EINVAL;
82c49f1a8aSRafał Miłecki 
83c49f1a8aSRafał Miłecki 	if (index)
84c49f1a8aSRafał Miłecki 		eth_addr_add(mac, index);
85c49f1a8aSRafał Miłecki 
86c49f1a8aSRafał Miłecki 	ether_addr_copy(buf, mac);
87c49f1a8aSRafał Miłecki 
88c49f1a8aSRafał Miłecki 	return 0;
89c49f1a8aSRafał Miłecki }
90c49f1a8aSRafał Miłecki 
u_boot_env_add_cells(struct u_boot_env * priv,uint8_t * buf,size_t data_offset,size_t data_len)91d5542923SRafał Miłecki static int u_boot_env_add_cells(struct u_boot_env *priv, uint8_t *buf,
92d5542923SRafał Miłecki 				size_t data_offset, size_t data_len)
93d5542923SRafał Miłecki {
94ae91c9c7SRafał Miłecki 	struct nvmem_device *nvmem = priv->nvmem;
95d5542923SRafał Miłecki 	struct device *dev = priv->dev;
96d5542923SRafał Miłecki 	char *data = buf + data_offset;
97d5542923SRafał Miłecki 	char *var, *value, *eq;
98d5542923SRafał Miłecki 
99ae91c9c7SRafał Miłecki 	for (var = data;
100d5542923SRafał Miłecki 	     var < data + data_len && *var;
101ae91c9c7SRafał Miłecki 	     var = value + strlen(value) + 1) {
102ae91c9c7SRafał Miłecki 		struct nvmem_cell_info info = {};
103ae91c9c7SRafał Miłecki 
104d5542923SRafał Miłecki 		eq = strchr(var, '=');
105d5542923SRafał Miłecki 		if (!eq)
106d5542923SRafał Miłecki 			break;
107d5542923SRafał Miłecki 		*eq = '\0';
108d5542923SRafał Miłecki 		value = eq + 1;
109d5542923SRafał Miłecki 
110ae91c9c7SRafał Miłecki 		info.name = devm_kstrdup(dev, var, GFP_KERNEL);
111ae91c9c7SRafał Miłecki 		if (!info.name)
112d5542923SRafał Miłecki 			return -ENOMEM;
113ae91c9c7SRafał Miłecki 		info.offset = data_offset + value - data;
114ae91c9c7SRafał Miłecki 		info.bytes = strlen(value);
115ae91c9c7SRafał Miłecki 		info.np = of_get_child_by_name(dev->of_node, info.name);
116c49f1a8aSRafał Miłecki 		if (!strcmp(var, "ethaddr")) {
117ae91c9c7SRafał Miłecki 			info.raw_len = strlen(value);
118ae91c9c7SRafał Miłecki 			info.bytes = ETH_ALEN;
119ae91c9c7SRafał Miłecki 			info.read_post_process = u_boot_env_read_post_process_ethaddr;
120d5542923SRafał Miłecki 		}
121d5542923SRafał Miłecki 
122ae91c9c7SRafał Miłecki 		nvmem_add_one_cell(nvmem, &info);
123ae91c9c7SRafał Miłecki 	}
124d5542923SRafał Miłecki 
125d5542923SRafał Miłecki 	return 0;
126d5542923SRafał Miłecki }
127d5542923SRafał Miłecki 
u_boot_env_parse(struct u_boot_env * priv)128d5542923SRafał Miłecki static int u_boot_env_parse(struct u_boot_env *priv)
129d5542923SRafał Miłecki {
1302eea394cSRafał Miłecki 	struct nvmem_device *nvmem = priv->nvmem;
131d5542923SRafał Miłecki 	struct device *dev = priv->dev;
132d5542923SRafał Miłecki 	size_t crc32_data_offset;
133d5542923SRafał Miłecki 	size_t crc32_data_len;
134d5542923SRafał Miłecki 	size_t crc32_offset;
135368fa77bSRafał Miłecki 	__le32 *crc32_addr;
136d5542923SRafał Miłecki 	size_t data_offset;
137d5542923SRafał Miłecki 	size_t data_len;
1382eea394cSRafał Miłecki 	size_t dev_size;
139d5542923SRafał Miłecki 	uint32_t crc32;
140d5542923SRafał Miłecki 	uint32_t calc;
141d5542923SRafał Miłecki 	uint8_t *buf;
1422eea394cSRafał Miłecki 	int bytes;
143d5542923SRafał Miłecki 	int err;
144d5542923SRafał Miłecki 
1452eea394cSRafał Miłecki 	dev_size = nvmem_dev_size(nvmem);
1462eea394cSRafał Miłecki 
147368fa77bSRafał Miłecki 	buf = kzalloc(dev_size, GFP_KERNEL);
148d5542923SRafał Miłecki 	if (!buf) {
149d5542923SRafał Miłecki 		err = -ENOMEM;
150d5542923SRafał Miłecki 		goto err_out;
151d5542923SRafał Miłecki 	}
152d5542923SRafał Miłecki 
1532eea394cSRafał Miłecki 	bytes = nvmem_device_read(nvmem, 0, dev_size, buf);
1542eea394cSRafał Miłecki 	if (bytes < 0) {
1552eea394cSRafał Miłecki 		err = bytes;
1562eea394cSRafał Miłecki 		goto err_kfree;
1572eea394cSRafał Miłecki 	} else if (bytes != dev_size) {
1582eea394cSRafał Miłecki 		err = -EIO;
159d5542923SRafał Miłecki 		goto err_kfree;
160d5542923SRafał Miłecki 	}
161d5542923SRafał Miłecki 
162d5542923SRafał Miłecki 	switch (priv->format) {
163d5542923SRafał Miłecki 	case U_BOOT_FORMAT_SINGLE:
164d5542923SRafał Miłecki 		crc32_offset = offsetof(struct u_boot_env_image_single, crc32);
165d5542923SRafał Miłecki 		crc32_data_offset = offsetof(struct u_boot_env_image_single, data);
166d5542923SRafał Miłecki 		data_offset = offsetof(struct u_boot_env_image_single, data);
167d5542923SRafał Miłecki 		break;
168d5542923SRafał Miłecki 	case U_BOOT_FORMAT_REDUNDANT:
169d5542923SRafał Miłecki 		crc32_offset = offsetof(struct u_boot_env_image_redundant, crc32);
170ee424f7dSChristian Lamparter 		crc32_data_offset = offsetof(struct u_boot_env_image_redundant, data);
171d5542923SRafał Miłecki 		data_offset = offsetof(struct u_boot_env_image_redundant, data);
172d5542923SRafał Miłecki 		break;
173ada84d07SRafał Miłecki 	case U_BOOT_FORMAT_BROADCOM:
174ada84d07SRafał Miłecki 		crc32_offset = offsetof(struct u_boot_env_image_broadcom, crc32);
175ada84d07SRafał Miłecki 		crc32_data_offset = offsetof(struct u_boot_env_image_broadcom, data);
176ada84d07SRafał Miłecki 		data_offset = offsetof(struct u_boot_env_image_broadcom, data);
177ada84d07SRafał Miłecki 		break;
178d5542923SRafał Miłecki 	}
179*2278629cSJohn Thomson 
180*2278629cSJohn Thomson 	if (dev_size < data_offset) {
181*2278629cSJohn Thomson 		dev_err(dev, "Device too small for u-boot-env\n");
182*2278629cSJohn Thomson 		err = -EIO;
183*2278629cSJohn Thomson 		goto err_kfree;
184*2278629cSJohn Thomson 	}
185*2278629cSJohn Thomson 
186368fa77bSRafał Miłecki 	crc32_addr = (__le32 *)(buf + crc32_offset);
187368fa77bSRafał Miłecki 	crc32 = le32_to_cpu(*crc32_addr);
1882eea394cSRafał Miłecki 	crc32_data_len = dev_size - crc32_data_offset;
1892eea394cSRafał Miłecki 	data_len = dev_size - data_offset;
190d5542923SRafał Miłecki 
191d5542923SRafał Miłecki 	calc = crc32(~0, buf + crc32_data_offset, crc32_data_len) ^ ~0L;
192d5542923SRafał Miłecki 	if (calc != crc32) {
193d5542923SRafał Miłecki 		dev_err(dev, "Invalid calculated CRC32: 0x%08x (expected: 0x%08x)\n", calc, crc32);
194d5542923SRafał Miłecki 		err = -EINVAL;
195d5542923SRafał Miłecki 		goto err_kfree;
196d5542923SRafał Miłecki 	}
197d5542923SRafał Miłecki 
1982eea394cSRafał Miłecki 	buf[dev_size - 1] = '\0';
199d5542923SRafał Miłecki 	err = u_boot_env_add_cells(priv, buf, data_offset, data_len);
200d5542923SRafał Miłecki 
201d5542923SRafał Miłecki err_kfree:
202d5542923SRafał Miłecki 	kfree(buf);
203d5542923SRafał Miłecki err_out:
204d5542923SRafał Miłecki 	return err;
205d5542923SRafał Miłecki }
206d5542923SRafał Miłecki 
u_boot_env_probe(struct platform_device * pdev)207d5542923SRafał Miłecki static int u_boot_env_probe(struct platform_device *pdev)
208d5542923SRafał Miłecki {
209d5542923SRafał Miłecki 	struct nvmem_config config = {
210d5542923SRafał Miłecki 		.name = "u-boot-env",
211d5542923SRafał Miłecki 		.reg_read = u_boot_env_read,
212d5542923SRafał Miłecki 	};
213d5542923SRafał Miłecki 	struct device *dev = &pdev->dev;
214d5542923SRafał Miłecki 	struct device_node *np = dev->of_node;
215d5542923SRafał Miłecki 	struct u_boot_env *priv;
216d5542923SRafał Miłecki 
217d5542923SRafał Miłecki 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
218d5542923SRafał Miłecki 	if (!priv)
219d5542923SRafał Miłecki 		return -ENOMEM;
220d5542923SRafał Miłecki 	priv->dev = dev;
221d5542923SRafał Miłecki 
222d5542923SRafał Miłecki 	priv->format = (uintptr_t)of_device_get_match_data(dev);
223d5542923SRafał Miłecki 
224d5542923SRafał Miłecki 	priv->mtd = of_get_mtd_device_by_node(np);
225d5542923SRafał Miłecki 	if (IS_ERR(priv->mtd)) {
226d5542923SRafał Miłecki 		dev_err_probe(dev, PTR_ERR(priv->mtd), "Failed to get %pOF MTD\n", np);
227d5542923SRafał Miłecki 		return PTR_ERR(priv->mtd);
228d5542923SRafał Miłecki 	}
229d5542923SRafał Miłecki 
230d5542923SRafał Miłecki 	config.dev = dev;
231d5542923SRafał Miłecki 	config.priv = priv;
232d5542923SRafał Miłecki 	config.size = priv->mtd->size;
233d5542923SRafał Miłecki 
234ae91c9c7SRafał Miłecki 	priv->nvmem = devm_nvmem_register(dev, &config);
235ae91c9c7SRafał Miłecki 	if (IS_ERR(priv->nvmem))
236ae91c9c7SRafał Miłecki 		return PTR_ERR(priv->nvmem);
237ae91c9c7SRafał Miłecki 
238ae91c9c7SRafał Miłecki 	return u_boot_env_parse(priv);
239d5542923SRafał Miłecki }
240d5542923SRafał Miłecki 
241d5542923SRafał Miłecki static const struct of_device_id u_boot_env_of_match_table[] = {
242d5542923SRafał Miłecki 	{ .compatible = "u-boot,env", .data = (void *)U_BOOT_FORMAT_SINGLE, },
243d5542923SRafał Miłecki 	{ .compatible = "u-boot,env-redundant-bool", .data = (void *)U_BOOT_FORMAT_REDUNDANT, },
244d5542923SRafał Miłecki 	{ .compatible = "u-boot,env-redundant-count", .data = (void *)U_BOOT_FORMAT_REDUNDANT, },
245ada84d07SRafał Miłecki 	{ .compatible = "brcm,env", .data = (void *)U_BOOT_FORMAT_BROADCOM, },
246d5542923SRafał Miłecki 	{},
247d5542923SRafał Miłecki };
248d5542923SRafał Miłecki 
249d5542923SRafał Miłecki static struct platform_driver u_boot_env_driver = {
250d5542923SRafał Miłecki 	.probe = u_boot_env_probe,
251d5542923SRafał Miłecki 	.driver = {
252d5542923SRafał Miłecki 		.name = "u_boot_env",
253d5542923SRafał Miłecki 		.of_match_table = u_boot_env_of_match_table,
254d5542923SRafał Miłecki 	},
255d5542923SRafał Miłecki };
256d5542923SRafał Miłecki module_platform_driver(u_boot_env_driver);
257d5542923SRafał Miłecki 
258d5542923SRafał Miłecki MODULE_AUTHOR("Rafał Miłecki");
259d5542923SRafał Miłecki MODULE_LICENSE("GPL");
260d5542923SRafał Miłecki MODULE_DEVICE_TABLE(of, u_boot_env_of_match_table);
261