File Index
Files and directories
Sign in
FILE

aquantia_firmware.c

File preview with syntax highlighting.

linux-6.19.14/drivers/net/phy/aquantia/aquantia_firmware.c C
  1// SPDX-License-Identifier: GPL-2.0
  2
  3#include <linux/bitfield.h>
  4#include <linux/of.h>
  5#include <linux/firmware.h>
  6#include <linux/crc-itu-t.h>
  7#include <linux/nvmem-consumer.h>
  8
  9#include <linux/unaligned.h>
 10
 11#include "aquantia.h"
 12
 13#define UP_RESET_SLEEP		100
 14
 15/* addresses of memory segments in the phy */
 16#define DRAM_BASE_ADDR		0x3FFE0000
 17#define IRAM_BASE_ADDR		0x40000000
 18
 19/* firmware image format constants */
 20#define VERSION_STRING_SIZE		0x40
 21#define VERSION_STRING_OFFSET		0x0200
 22/* primary offset is written at an offset from the start of the fw blob */
 23#define PRIMARY_OFFSET_OFFSET		0x8
 24/* primary offset needs to be then added to a base offset */
 25#define PRIMARY_OFFSET_SHIFT		12
 26#define PRIMARY_OFFSET(x)		((x) << PRIMARY_OFFSET_SHIFT)
 27#define HEADER_OFFSET			0x300
 28
 29struct aqr_fw_header {
 30	u32 padding;
 31	u8 iram_offset[3];
 32	u8 iram_size[3];
 33	u8 dram_offset[3];
 34	u8 dram_size[3];
 35} __packed;
 36
 37enum aqr_fw_src {
 38	AQR_FW_SRC_NVMEM = 0,
 39	AQR_FW_SRC_FS,
 40};
 41
 42static const char * const aqr_fw_src_string[] = {
 43	[AQR_FW_SRC_NVMEM] = "NVMEM",
 44	[AQR_FW_SRC_FS] = "FS",
 45};
 46
 47/* AQR firmware doesn't have fixed offsets for iram and dram section
 48 * but instead provide an header with the offset to use on reading
 49 * and parsing the firmware.
 50 *
 51 * AQR firmware can't be trusted and each offset is validated to be
 52 * not negative and be in the size of the firmware itself.
 53 */
 54static bool aqr_fw_validate_get(size_t size, size_t offset, size_t get_size)
 55{
 56	return offset + get_size <= size;
 57}
 58
 59static int aqr_fw_get_be16(const u8 *data, size_t offset, size_t size, u16 *value)
 60{
 61	if (!aqr_fw_validate_get(size, offset, sizeof(u16)))
 62		return -EINVAL;
 63
 64	*value = get_unaligned_be16(data + offset);
 65
 66	return 0;
 67}
 68
 69static int aqr_fw_get_le16(const u8 *data, size_t offset, size_t size, u16 *value)
 70{
 71	if (!aqr_fw_validate_get(size, offset, sizeof(u16)))
 72		return -EINVAL;
 73
 74	*value = get_unaligned_le16(data + offset);
 75
 76	return 0;
 77}
 78
 79static int aqr_fw_get_le24(const u8 *data, size_t offset, size_t size, u32 *value)
 80{
 81	if (!aqr_fw_validate_get(size, offset, sizeof(u8) * 3))
 82		return -EINVAL;
 83
 84	*value = get_unaligned_le24(data + offset);
 85
 86	return 0;
 87}
 88
 89/* load data into the phy's memory */
 90static int aqr_fw_load_memory(struct phy_device *phydev, u32 addr,
 91			      const u8 *data, size_t len)
 92{
 93	u16 crc = 0, up_crc;
 94	size_t pos;
 95
 96	phy_write_mmd(phydev, MDIO_MMD_VEND1,
 97		      VEND1_GLOBAL_MAILBOX_INTERFACE1,
 98		      VEND1_GLOBAL_MAILBOX_INTERFACE1_CRC_RESET);
 99	phy_write_mmd(phydev, MDIO_MMD_VEND1,
100		      VEND1_GLOBAL_MAILBOX_INTERFACE3,
101		      VEND1_GLOBAL_MAILBOX_INTERFACE3_MSW_ADDR(addr));
102	phy_write_mmd(phydev, MDIO_MMD_VEND1,
103		      VEND1_GLOBAL_MAILBOX_INTERFACE4,
104		      VEND1_GLOBAL_MAILBOX_INTERFACE4_LSW_ADDR(addr));
105
106	/* We assume and enforce the size to be word aligned.
107	 * If a firmware that is not word aligned is found, please report upstream.
108	 */
109	for (pos = 0; pos < len; pos += sizeof(u32)) {
110		u8 crc_data[4];
111		u32 word;
112
113		/* FW data is always stored in little-endian */
114		word = get_unaligned_le32((const u32 *)(data + pos));
115
116		phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_MAILBOX_INTERFACE5,
117			      VEND1_GLOBAL_MAILBOX_INTERFACE5_MSW_DATA(word));
118		phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_MAILBOX_INTERFACE6,
119			      VEND1_GLOBAL_MAILBOX_INTERFACE6_LSW_DATA(word));
120
121		phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_MAILBOX_INTERFACE1,
122			      VEND1_GLOBAL_MAILBOX_INTERFACE1_EXECUTE |
123			      VEND1_GLOBAL_MAILBOX_INTERFACE1_WRITE);
124
125		/* Word is swapped internally and MAILBOX CRC is calculated
126		 * using big-endian order. Mimic what the PHY does to have a
127		 * matching CRC...
128		 */
129		crc_data[0] = word >> 24;
130		crc_data[1] = word >> 16;
131		crc_data[2] = word >> 8;
132		crc_data[3] = word;
133
134		/* ...calculate CRC as we load data... */
135		crc = crc_itu_t(crc, crc_data, sizeof(crc_data));
136	}
137	/* ...gets CRC from MAILBOX after we have loaded the entire section... */
138	up_crc = phy_read_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_MAILBOX_INTERFACE2);
139	/* ...and make sure it does match our calculated CRC */
140	if (crc != up_crc) {
141		phydev_err(phydev, "CRC mismatch: calculated 0x%04x PHY 0x%04x\n",
142			   crc, up_crc);
143		return -EINVAL;
144	}
145
146	return 0;
147}
148
149static int aqr_fw_boot(struct phy_device *phydev, const u8 *data, size_t size,
150		       enum aqr_fw_src fw_src)
151{
152	u16 calculated_crc, read_crc, read_primary_offset;
153	u32 iram_offset = 0, iram_size = 0;
154	u32 dram_offset = 0, dram_size = 0;
155	char version[VERSION_STRING_SIZE];
156	u32 primary_offset = 0;
157	int ret;
158
159	/* extract saved CRC at the end of the fw
160	 * CRC is saved in big-endian as PHY is BE
161	 */
162	ret = aqr_fw_get_be16(data, size - sizeof(u16), size, &read_crc);
163	if (ret) {
164		phydev_err(phydev, "bad firmware CRC in firmware\n");
165		return ret;
166	}
167	calculated_crc = crc_itu_t(0, data, size - sizeof(u16));
168	if (read_crc != calculated_crc) {
169		phydev_err(phydev, "bad firmware CRC: file 0x%04x calculated 0x%04x\n",
170			   read_crc, calculated_crc);
171		return -EINVAL;
172	}
173
174	/* Get the primary offset to extract DRAM and IRAM sections. */
175	ret = aqr_fw_get_le16(data, PRIMARY_OFFSET_OFFSET, size, &read_primary_offset);
176	if (ret) {
177		phydev_err(phydev, "bad primary offset in firmware\n");
178		return ret;
179	}
180	primary_offset = PRIMARY_OFFSET(read_primary_offset);
181
182	/* Find the DRAM and IRAM sections within the firmware file.
183	 * Make sure the fw_header is correctly in the firmware.
184	 */
185	if (!aqr_fw_validate_get(size, primary_offset + HEADER_OFFSET,
186				 sizeof(struct aqr_fw_header))) {
187		phydev_err(phydev, "bad fw_header in firmware\n");
188		return -EINVAL;
189	}
190
191	/* offset are in LE and values needs to be converted to cpu endian */
192	ret = aqr_fw_get_le24(data, primary_offset + HEADER_OFFSET +
193			      offsetof(struct aqr_fw_header, iram_offset),
194			      size, &iram_offset);
195	if (ret) {
196		phydev_err(phydev, "bad iram offset in firmware\n");
197		return ret;
198	}
199	ret = aqr_fw_get_le24(data, primary_offset + HEADER_OFFSET +
200			      offsetof(struct aqr_fw_header, iram_size),
201			      size, &iram_size);
202	if (ret) {
203		phydev_err(phydev, "invalid iram size in firmware\n");
204		return ret;
205	}
206	ret = aqr_fw_get_le24(data, primary_offset + HEADER_OFFSET +
207			      offsetof(struct aqr_fw_header, dram_offset),
208			      size, &dram_offset);
209	if (ret) {
210		phydev_err(phydev, "bad dram offset in firmware\n");
211		return ret;
212	}
213	ret = aqr_fw_get_le24(data, primary_offset + HEADER_OFFSET +
214			      offsetof(struct aqr_fw_header, dram_size),
215			      size, &dram_size);
216	if (ret) {
217		phydev_err(phydev, "invalid dram size in firmware\n");
218		return ret;
219	}
220
221	/* Increment the offset with the primary offset.
222	 * Validate iram/dram offset and size.
223	 */
224	iram_offset += primary_offset;
225	if (iram_size % sizeof(u32)) {
226		phydev_err(phydev, "iram size if not aligned to word size. Please report this upstream!\n");
227		return -EINVAL;
228	}
229	if (!aqr_fw_validate_get(size, iram_offset, iram_size)) {
230		phydev_err(phydev, "invalid iram offset for iram size\n");
231		return -EINVAL;
232	}
233
234	dram_offset += primary_offset;
235	if (dram_size % sizeof(u32)) {
236		phydev_err(phydev, "dram size if not aligned to word size. Please report this upstream!\n");
237		return -EINVAL;
238	}
239	if (!aqr_fw_validate_get(size, dram_offset, dram_size)) {
240		phydev_err(phydev, "invalid iram offset for iram size\n");
241		return -EINVAL;
242	}
243
244	phydev_dbg(phydev, "primary %d IRAM offset=%d size=%d DRAM offset=%d size=%d\n",
245		   primary_offset, iram_offset, iram_size, dram_offset, dram_size);
246
247	if (!aqr_fw_validate_get(size, dram_offset + VERSION_STRING_OFFSET,
248				 VERSION_STRING_SIZE)) {
249		phydev_err(phydev, "invalid version in firmware\n");
250		return -EINVAL;
251	}
252	strscpy(version, (char *)data + dram_offset + VERSION_STRING_OFFSET,
253		VERSION_STRING_SIZE);
254	if (version[0] == '\0') {
255		phydev_err(phydev, "invalid version in firmware\n");
256		return -EINVAL;
257	}
258	phydev_info(phydev, "loading firmware version '%s' from '%s'\n", version,
259		    aqr_fw_src_string[fw_src]);
260
261	/* stall the microcprocessor */
262	phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_CONTROL2,
263		      VEND1_GLOBAL_CONTROL2_UP_RUN_STALL | VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_OVD);
264
265	phydev_dbg(phydev, "loading DRAM 0x%08x from offset=%d size=%d\n",
266		   DRAM_BASE_ADDR, dram_offset, dram_size);
267	ret = aqr_fw_load_memory(phydev, DRAM_BASE_ADDR, data + dram_offset,
268				 dram_size);
269	if (ret)
270		return ret;
271
272	phydev_dbg(phydev, "loading IRAM 0x%08x from offset=%d size=%d\n",
273		   IRAM_BASE_ADDR, iram_offset, iram_size);
274	ret = aqr_fw_load_memory(phydev, IRAM_BASE_ADDR, data + iram_offset,
275				 iram_size);
276	if (ret)
277		return ret;
278
279	/* make sure soft reset and low power mode are clear */
280	phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_SC,
281			   VEND1_GLOBAL_SC_SOFT_RESET | VEND1_GLOBAL_SC_LOW_POWER);
282
283	/* Release the microprocessor. UP_RESET must be held for 100 usec. */
284	phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_CONTROL2,
285		      VEND1_GLOBAL_CONTROL2_UP_RUN_STALL |
286		      VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_OVD |
287		      VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_RST);
288	usleep_range(UP_RESET_SLEEP, UP_RESET_SLEEP * 2);
289
290	phy_write_mmd(phydev, MDIO_MMD_VEND1, VEND1_GLOBAL_CONTROL2,
291		      VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_OVD);
292
293	return 0;
294}
295
296static int aqr_firmware_load_nvmem(struct phy_device *phydev)
297{
298	struct nvmem_cell *cell;
299	size_t size;
300	u8 *buf;
301	int ret;
302
303	cell = nvmem_cell_get(&phydev->mdio.dev, "firmware");
304	if (IS_ERR(cell))
305		return PTR_ERR(cell);
306
307	buf = nvmem_cell_read(cell, &size);
308	if (IS_ERR(buf)) {
309		ret = PTR_ERR(buf);
310		goto exit;
311	}
312
313	ret = aqr_fw_boot(phydev, buf, size, AQR_FW_SRC_NVMEM);
314	if (ret)
315		phydev_err(phydev, "firmware loading failed: %d\n", ret);
316
317	kfree(buf);
318exit:
319	nvmem_cell_put(cell);
320
321	return ret;
322}
323
324static int aqr_firmware_load_fs(struct phy_device *phydev)
325{
326	struct device *dev = &phydev->mdio.dev;
327	const struct firmware *fw;
328	const char *fw_name;
329	int ret;
330
331	ret = device_property_read_string(dev, "firmware-name", &fw_name);
332	if (ret) {
333		phydev_err(phydev, "failed to read firmware-name: %d\n", ret);
334		return ret;
335	}
336
337	ret = request_firmware(&fw, fw_name, dev);
338	if (ret) {
339		phydev_err(phydev, "failed to find FW file %s (%d)\n",
340			   fw_name, ret);
341		return ret;
342	}
343
344	ret = aqr_fw_boot(phydev, fw->data, fw->size, AQR_FW_SRC_FS);
345	if (ret)
346		phydev_err(phydev, "firmware loading failed: %d\n", ret);
347
348	release_firmware(fw);
349
350	return ret;
351}
352
353int aqr_firmware_load(struct phy_device *phydev)
354{
355	int ret;
356
357	/* Check if the firmware is not already loaded by polling
358	 * the current version returned by the PHY.
359	 */
360	ret = aqr_wait_reset_complete(phydev);
361	switch (ret) {
362	case 0:
363		/* Some firmware is loaded => do nothing */
364		return 0;
365	case -ETIMEDOUT:
366		/* VEND1_GLOBAL_FW_ID still reads 0 after 2 seconds of polling.
367		 * We don't have full confidence that no firmware is loaded (in
368		 * theory it might just not have loaded yet), but we will
369		 * assume that, and load a new image.
370		 */
371		ret = aqr_firmware_load_nvmem(phydev);
372		if (ret == -EPROBE_DEFER || !ret)
373			return ret;
374
375		ret = aqr_firmware_load_fs(phydev);
376		if (ret)
377			return ret;
378		break;
379	default:
380		/* PHY read error, propagate it to the caller */
381		return ret;
382	}
383
384	return 0;
385}
~/ Read-only file preview.