1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (c) 2023, Linaro Limited
4  */
5 
6 #define LOG_CATEGORY UCLASS_FWU_MDATA
7 
8 #include <fwu.h>
9 #include <fwu_mdata.h>
10 #include <memalign.h>
11 #include <mtd.h>
12 
13 #include <linux/errno.h>
14 #include <linux/types.h>
15 
16 enum fwu_mtd_op {
17 	FWU_MTD_READ,
18 	FWU_MTD_WRITE,
19 };
20 
mtd_is_aligned_with_block_size(struct mtd_info * mtd,u64 size)21 static bool mtd_is_aligned_with_block_size(struct mtd_info *mtd, u64 size)
22 {
23 	return !do_div(size, mtd->erasesize);
24 }
25 
mtd_io_data(struct mtd_info * mtd,u32 offs,u32 size,void * data,enum fwu_mtd_op op)26 static int mtd_io_data(struct mtd_info *mtd, u32 offs, u32 size, void *data,
27 		       enum fwu_mtd_op op)
28 {
29 	struct mtd_oob_ops io_op = {};
30 	u64 lock_len;
31 	size_t len;
32 	void *buf;
33 	int ret;
34 
35 	if (!mtd_is_aligned_with_block_size(mtd, offs)) {
36 		log_err("Offset unaligned with a block (0x%x)\n", mtd->erasesize);
37 		return -EINVAL;
38 	}
39 
40 	/* This will expand erase size to align with the block size */
41 	lock_len = round_up(size, mtd->erasesize);
42 
43 	ret = mtd_unlock(mtd, offs, lock_len);
44 	if (ret && ret != -EOPNOTSUPP)
45 		return ret;
46 
47 	if (op == FWU_MTD_WRITE) {
48 		struct erase_info erase_op = {};
49 
50 		erase_op.mtd = mtd;
51 		erase_op.addr = offs;
52 		erase_op.len = lock_len;
53 		erase_op.scrub = 0;
54 
55 		ret = mtd_erase(mtd, &erase_op);
56 		if (ret)
57 			goto lock;
58 	}
59 
60 	/* Also, expand the write size to align with the write size */
61 	len = round_up(size, mtd->writesize);
62 
63 	buf = memalign(ARCH_DMA_MINALIGN, len);
64 	if (!buf) {
65 		ret = -ENOMEM;
66 		goto lock;
67 	}
68 	memset(buf, 0xff, len);
69 
70 	io_op.mode = MTD_OPS_AUTO_OOB;
71 	io_op.len = len;
72 	io_op.datbuf = buf;
73 
74 	if (op == FWU_MTD_WRITE) {
75 		memcpy(buf, data, size);
76 		ret = mtd_write_oob(mtd, offs, &io_op);
77 	} else {
78 		ret = mtd_read_oob(mtd, offs, &io_op);
79 		if (!ret)
80 			memcpy(data, buf, size);
81 	}
82 	free(buf);
83 
84 lock:
85 	mtd_lock(mtd, offs, lock_len);
86 
87 	return ret;
88 }
89 
fwu_mtd_read_mdata(struct udevice * dev,struct fwu_mdata * mdata,bool primary,u32 size)90 static int fwu_mtd_read_mdata(struct udevice *dev, struct fwu_mdata *mdata,
91 			      bool primary, u32 size)
92 {
93 	struct fwu_mdata_mtd_priv *mtd_priv = dev_get_priv(dev);
94 	struct mtd_info *mtd = mtd_priv->mtd;
95 	u32 offs = primary ? mtd_priv->pri_offset : mtd_priv->sec_offset;
96 
97 	return mtd_io_data(mtd, offs, size, mdata, FWU_MTD_READ);
98 }
99 
fwu_mtd_write_mdata(struct udevice * dev,struct fwu_mdata * mdata,bool primary,u32 size)100 static int fwu_mtd_write_mdata(struct udevice *dev, struct fwu_mdata *mdata,
101 			       bool primary, u32 size)
102 {
103 	struct fwu_mdata_mtd_priv *mtd_priv = dev_get_priv(dev);
104 	struct mtd_info *mtd = mtd_priv->mtd;
105 	u32 offs = primary ? mtd_priv->pri_offset : mtd_priv->sec_offset;
106 
107 	return mtd_io_data(mtd, offs, size, mdata, FWU_MTD_WRITE);
108 }
109 
flash_partition_offset(struct udevice * dev,const char * part_name,fdt_addr_t * offset)110 static int flash_partition_offset(struct udevice *dev, const char *part_name, fdt_addr_t *offset)
111 {
112 	ofnode node, parts_node;
113 	fdt_addr_t size = 0;
114 
115 	parts_node = ofnode_by_compatible(dev_ofnode(dev), "fixed-partitions");
116 	node = ofnode_by_prop_value(parts_node, "label", part_name, strlen(part_name) + 1);
117 	if (!ofnode_valid(node)) {
118 		log_err("Warning: Failed to find partition by label <%s>\n", part_name);
119 		return -ENOENT;
120 	}
121 
122 	*offset = ofnode_get_addr_size_index_notrans(node, 0, &size);
123 
124 	return (int)size;
125 }
126 
get_fwu_mdata_dev(struct udevice * dev)127 static int get_fwu_mdata_dev(struct udevice *dev)
128 {
129 	struct fwu_mdata_mtd_priv *mtd_priv = dev_get_priv(dev);
130 	const fdt32_t *phandle_p = NULL;
131 	struct udevice *mtd_dev;
132 	struct mtd_info *mtd;
133 	const char *label;
134 	fdt_addr_t offset;
135 	int ret, size;
136 	u32 phandle;
137 
138 	/* Find the FWU mdata storage device */
139 	phandle_p = ofnode_get_property(dev_ofnode(dev),
140 					"fwu-mdata-store", &size);
141 	if (!phandle_p) {
142 		log_err("FWU meta data store not defined in device-tree\n");
143 		return -ENOENT;
144 	}
145 
146 	phandle = fdt32_to_cpu(*phandle_p);
147 
148 	ret = device_get_global_by_ofnode(ofnode_get_by_phandle(phandle),
149 					  &mtd_dev);
150 	if (ret) {
151 		log_err("FWU: failed to get mtd device\n");
152 		return ret;
153 	}
154 
155 	mtd_probe_devices();
156 
157 	mtd_for_each_device(mtd) {
158 		if (mtd->dev == mtd_dev) {
159 			mtd_priv->mtd = mtd;
160 			log_debug("Found the FWU mdata mtd device %s\n", mtd->name);
161 			break;
162 		}
163 	}
164 	if (!mtd_priv->mtd) {
165 		log_err("Failed to find mtd device by fwu-mdata-store\n");
166 		return -ENODEV;
167 	}
168 
169 	/* Get the offset of primary and secondary mdata */
170 	ret = ofnode_read_string_index(dev_ofnode(dev), "mdata-parts", 0, &label);
171 	if (ret)
172 		return ret;
173 	strncpy(mtd_priv->pri_label, label, 50);
174 
175 	ret = flash_partition_offset(mtd_dev, mtd_priv->pri_label, &offset);
176 	if (ret <= 0)
177 		return ret;
178 	mtd_priv->pri_offset = offset;
179 
180 	ret = ofnode_read_string_index(dev_ofnode(dev), "mdata-parts", 1, &label);
181 	if (ret)
182 		return ret;
183 	strncpy(mtd_priv->sec_label, label, 50);
184 
185 	ret = flash_partition_offset(mtd_dev, mtd_priv->sec_label, &offset);
186 	if (ret <= 0)
187 		return ret;
188 	mtd_priv->sec_offset = offset;
189 
190 	return 0;
191 }
192 
fwu_mtd_image_info_populate(struct udevice * dev,u8 nbanks,u16 nimages)193 static int fwu_mtd_image_info_populate(struct udevice *dev, u8 nbanks,
194 				       u16 nimages)
195 {
196 	struct fwu_mtd_image_info *mtd_images;
197 	struct fwu_mdata_mtd_priv *mtd_priv = dev_get_priv(dev);
198 	struct udevice *mtd_dev = mtd_priv->mtd->dev;
199 	fdt_addr_t offset;
200 	ofnode bank;
201 	int off_img;
202 	u32 total_images;
203 
204 	total_images = nbanks * nimages;
205 	mtd_priv->fwu_mtd_images = malloc(sizeof(struct fwu_mtd_image_info) *
206 					  total_images);
207 	if (!mtd_priv->fwu_mtd_images)
208 		return -ENOMEM;
209 
210 	off_img = 0;
211 	mtd_images = mtd_priv->fwu_mtd_images;
212 	ofnode_for_each_subnode(bank, dev_ofnode(dev)) {
213 		int bank_num, bank_offset, bank_size;
214 		const char *bank_name;
215 		ofnode image;
216 
217 		ofnode_read_u32(bank, "id", &bank_num);
218 		bank_name = ofnode_read_string(bank, "label");
219 		bank_size = flash_partition_offset(mtd_dev, bank_name, &offset);
220 		if (bank_size <= 0)
221 			return bank_size;
222 		bank_offset = offset;
223 		log_debug("Bank%d: %s [0x%x - 0x%x]\n",
224 			  bank_num, bank_name, bank_offset, bank_offset + bank_size);
225 
226 		ofnode_for_each_subnode(image, bank) {
227 			int image_num, image_offset, image_size;
228 			const char *uuid;
229 
230 			if (off_img == total_images) {
231 				log_err("DT provides more images than configured!\n");
232 				break;
233 			}
234 
235 			uuid = ofnode_read_string(image, "uuid");
236 			ofnode_read_u32(image, "id", &image_num);
237 			ofnode_read_u32(image, "offset", &image_offset);
238 			ofnode_read_u32(image, "size", &image_size);
239 
240 			mtd_images[off_img].start = bank_offset + image_offset;
241 			mtd_images[off_img].size = image_size;
242 			mtd_images[off_img].bank_num = bank_num;
243 			mtd_images[off_img].image_num = image_num;
244 			strcpy(mtd_images[off_img].uuidbuf, uuid);
245 			log_debug("\tImage%d: %s @0x%x\n\n",
246 				  image_num, uuid, bank_offset + image_offset);
247 			off_img++;
248 		}
249 	}
250 
251 	return 0;
252 }
253 
fwu_mdata_mtd_probe(struct udevice * dev)254 static int fwu_mdata_mtd_probe(struct udevice *dev)
255 {
256 	u8 nbanks;
257 	u16 nimages;
258 	int ret;
259 
260 	ret = get_fwu_mdata_dev(dev);
261 	if (ret)
262 		return ret;
263 
264 	nbanks = CONFIG_FWU_NUM_BANKS;
265 	nimages = CONFIG_FWU_NUM_IMAGES_PER_BANK;
266 	ret = fwu_mtd_image_info_populate(dev, nbanks, nimages);
267 	if (ret)
268 		return ret;
269 
270 	return 0;
271 }
272 
273 static struct fwu_mdata_ops fwu_mtd_ops = {
274 	.read_mdata = fwu_mtd_read_mdata,
275 	.write_mdata = fwu_mtd_write_mdata,
276 };
277 
278 static const struct udevice_id fwu_mdata_ids[] = {
279 	{ .compatible = "u-boot,fwu-mdata-mtd" },
280 	{ }
281 };
282 
283 U_BOOT_DRIVER(fwu_mdata_mtd) = {
284 	.name		= "fwu-mdata-mtd",
285 	.id		= UCLASS_FWU_MDATA,
286 	.of_match	= fwu_mdata_ids,
287 	.ops		= &fwu_mtd_ops,
288 	.probe		= fwu_mdata_mtd_probe,
289 	.priv_auto	= sizeof(struct fwu_mdata_mtd_priv),
290 };
291