On Wed, Nov 01, 2023 at 01:36:07PM +0100, Christian Marangi wrote: > From: Robert Marko <robimarko@xxxxxxxxx> > > Aquantia PHY-s require firmware to be loaded before they start operating. > It can be automatically loaded in case when there is a SPI-NOR connected > to Aquantia PHY-s or can be loaded from the host via MDIO. > > This patch adds support for loading the firmware via MDIO as in most cases > there is no SPI-NOR being used to save on cost. > Firmware loading code itself is ported from mainline U-boot with cleanups. > > The firmware has mixed values both in big and little endian. > PHY core itself is big-endian but it expects values to be in little-endian. > The firmware is little-endian but CRC-16 value for it is stored at the end > of firmware in big-endian. > > It seems the PHY does the conversion internally from firmware that is > little-endian to the PHY that is big-endian on using the mailbox > but mailbox returns a big-endian CRC-16 to verify the written data > integrity. > > Co-developed-by: Christian Marangi <ansuelsmth@xxxxxxxxx> > Signed-off-by: Robert Marko <robimarko@xxxxxxxxx> > Signed-off-by: Christian Marangi <ansuelsmth@xxxxxxxxx> > --- > Changes v2: > - Move out of RFC Actually, since we are in the merge window, RFC would be correct. > - Address sanity check for offsets > - Add additional comments on firmware load check > - Fix some typo > - Capitalize CRC in comments > - Rename load_sysfs to load_fs > > drivers/net/phy/Kconfig | 1 + > drivers/net/phy/aquantia_main.c | 304 ++++++++++++++++++++++++++++++++ > 2 files changed, 305 insertions(+) > > diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig > index 421d2b62918f..46c7194efcea 100644 > --- a/drivers/net/phy/Kconfig > +++ b/drivers/net/phy/Kconfig > @@ -98,6 +98,7 @@ config ADIN1100_PHY > > config AQUANTIA_PHY > tristate "Aquantia PHYs" > + select CRC_CCITT > help > Currently supports the Aquantia AQ1202, AQ2104, AQR105, AQR405 > > diff --git a/drivers/net/phy/aquantia_main.c b/drivers/net/phy/aquantia_main.c > index 334a6904ca5a..0f1b8d75cca0 100644 > --- a/drivers/net/phy/aquantia_main.c > +++ b/drivers/net/phy/aquantia_main.c > @@ -12,6 +12,10 @@ > #include <linux/delay.h> > #include <linux/bitfield.h> > #include <linux/phy.h> > +#include <linux/of.h> > +#include <linux/firmware.h> > +#include <linux/crc-ccitt.h> > +#include <linux/nvmem-consumer.h> > > #include "aquantia.h" > > @@ -92,10 +96,40 @@ > #define MDIO_C22EXT_STAT_SGMII_TX_RUNT_FRAMES 0xd31b > > /* Vendor specific 1, MDIO_MMD_VEND1 */ > +#define VEND1_GLOBAL_SC 0x0 > +#define VEND1_GLOBAL_SC_SOFT_RESET BIT(15) > +#define VEND1_GLOBAL_SC_LOW_POWER BIT(11) > + > #define VEND1_GLOBAL_FW_ID 0x0020 > #define VEND1_GLOBAL_FW_ID_MAJOR GENMASK(15, 8) > #define VEND1_GLOBAL_FW_ID_MINOR GENMASK(7, 0) > > +#define VEND1_GLOBAL_MAILBOX_INTERFACE1 0x0200 > +#define VEND1_GLOBAL_MAILBOX_INTERFACE1_EXECUTE BIT(15) > +#define VEND1_GLOBAL_MAILBOX_INTERFACE1_WRITE BIT(14) > +#define VEND1_GLOBAL_MAILBOX_INTERFACE1_CRC_RESET BIT(12) > +#define VEND1_GLOBAL_MAILBOX_INTERFACE1_BUSY BIT(8) > + > +#define VEND1_GLOBAL_MAILBOX_INTERFACE2 0x0201 > +#define VEND1_GLOBAL_MAILBOX_INTERFACE3 0x0202 > +#define VEND1_GLOBAL_MAILBOX_INTERFACE3_MSW_ADDR_MASK GENMASK(15, 0) > +#define VEND1_GLOBAL_MAILBOX_INTERFACE3_MSW_ADDR(x) FIELD_PREP(VEND1_GLOBAL_MAILBOX_INTERFACE3_MSW_ADDR_MASK, (u16)((x) >> 16)) > +#define VEND1_GLOBAL_MAILBOX_INTERFACE4 0x0203 > +#define VEND1_GLOBAL_MAILBOX_INTERFACE4_LSW_ADDR_MASK GENMASK(15, 2) > +#define VEND1_GLOBAL_MAILBOX_INTERFACE4_LSW_ADDR(x) FIELD_PREP(VEND1_GLOBAL_MAILBOX_INTERFACE4_LSW_ADDR_MASK, (u16)(x)) > + > +#define VEND1_GLOBAL_MAILBOX_INTERFACE5 0x0204 > +#define VEND1_GLOBAL_MAILBOX_INTERFACE5_MSW_DATA_MASK GENMASK(15, 0) > +#define VEND1_GLOBAL_MAILBOX_INTERFACE5_MSW_DATA(x) FIELD_PREP(VEND1_GLOBAL_MAILBOX_INTERFACE5_MSW_DATA_MASK, (u16)((x) >> 16)) > +#define VEND1_GLOBAL_MAILBOX_INTERFACE6 0x0205 > +#define VEND1_GLOBAL_MAILBOX_INTERFACE6_LSW_DATA_MASK GENMASK(15, 0) > +#define VEND1_GLOBAL_MAILBOX_INTERFACE6_LSW_DATA(x) FIELD_PREP(VEND1_GLOBAL_MAILBOX_INTERFACE6_LSW_DATA_MASK, (u16)(x)) > + > +#define VEND1_GLOBAL_CONTROL2 0xc001 > +#define VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_RST BIT(15) > +#define VEND1_GLOBAL_CONTROL2_UP_RUN_STALL_OVD BIT(6) > +#define VEND1_GLOBAL_CONTROL2_UP_RUN_STALL BIT(0) > + > #define VEND1_GLOBAL_GEN_STAT2 0xc831 > #define VEND1_GLOBAL_GEN_STAT2_OP_IN_PROG BIT(15) > > @@ -152,6 +186,30 @@ > #define AQR107_OP_IN_PROG_SLEEP 1000 > #define AQR107_OP_IN_PROG_TIMEOUT 100000 > > +#define UP_RESET_SLEEP 100 > + > +/* addresses of memory segments in the phy */ > +#define DRAM_BASE_ADDR 0x3FFE0000 > +#define IRAM_BASE_ADDR 0x40000000 > + > +/* firmware image format constants */ > +#define VERSION_STRING_SIZE 0x40 > +#define VERSION_STRING_OFFSET 0x0200 > +/* primary offset is written at an offset from the start of the fw blob */ > +#define PRIMARY_OFFSET_OFFSET 0x8 > +/* primary offset needs to be then added to a base offset */ > +#define PRIMARY_OFFSET_SHIFT 12 > +#define PRIMARY_OFFSET(x) ((x) << PRIMARY_OFFSET_SHIFT) > +#define HEADER_OFFSET 0x300 > + > +struct aqr_fw_header { > + u32 padding; > + u8 iram_offset[3]; > + u8 iram_size[3]; > + u8 dram_offset[3]; > + u8 dram_size[3]; > +} __packed; > + > struct aqr107_hw_stat { > const char *name; > int reg; > @@ -677,6 +735,166 @@ static int aqr107_wait_processor_intensive_op(struct phy_device *phydev) > return 0; > } > > +/* load data into the phy's memory */ > +static int aquantia_load_memory(struct phy_device *phydev, u32 addr, > + const u8 *data, size_t len) > +{ > + for (pos = 0; pos < len; pos += min(sizeof(u32), len - pos)) { > + u32 word = 0; > + > + memcpy(&word, data + pos, min(sizeof(u32), len - pos)); Rather than do a memcpy, use the get_unaligned_ macros. They might map to a memcpy(), but some architectures can do unaligned accesses without problems. > +static int aqr_fw_boot(struct phy_device *phydev, const u8 *data, size_t size) > +{ > + const struct aqr_fw_header *header; > + u32 iram_offset = 0, iram_size = 0; > + u32 dram_offset = 0, dram_size = 0; > + char version[VERSION_STRING_SIZE]; > + u16 calculated_crc, read_crc; > + u32 primary_offset = 0; > + int ret; > + > + /* extract saved CRC at the end of the fw */ > + memcpy(&read_crc, data + size - 2, sizeof(read_crc)); Say size == 1. You just had a buffer underrun. > + /* CRC is saved in big-endian as PHY is BE */ > + read_crc = be16_to_cpu(read_crc); > + calculated_crc = crc_ccitt_false(0, data, size - 2); > + if (read_crc != calculated_crc) { > + phydev_err(phydev, "bad firmware CRC: file 0x%04x calculated 0x%04x\n", > + read_crc, calculated_crc); > + return -EINVAL; > + } > + > + /* Get the primary offset to extract DRAM and IRAM sections. */ > + memcpy(&primary_offset, data + PRIMARY_OFFSET_OFFSET, sizeof(u16)); What if PRIMARY_OFFSET_OFFSET + sizeof(u16) is greater than size? A buffer overrun. Assume the firmware is evil and is trying to hack you. Always test everything. I would suggest some helpers, something like int aqr_fw_get_u16(const u8 *data, size_t size, size_t offset, u16 *value) Check that offset + sizeof(u16) is within the firmware, and if not return -EINVAL. Otherwise set *value to the u16 from the firmware and return 0. This is where Rust would be nice :-) Andrew --- pw-bot: cr