Re: [PATCH v5 3/3] remoteproc: Add AVM WASP driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Daniel,

On Thu, Aug 04, 2022 at 11:08:06PM +0200, Daniel Kestrel wrote:
> Some AVM Fritzbox router boards (3390, 3490, 5490, 5491, 7490),
> that are Lantiq XRX200 based, have a memory only ATH79 based
> WASP (Wireless Assistant Support Processor) SoC that has wifi
> cards connected to it. It does not share anything with the
> Lantiq host and has no persistent storage. It has an mdio based
> connection for bringing up a small network boot firmware and is
> connected to the Lantiq GSWIP switch via gigabit ethernet. This
> is used to load an initramfs linux image to it, after the
> network boot firmware was started.
> 
> In order to initialize this remote processor we need to:
> - power on the SoC using power gpio
> - reset the SoC using the reset gpio
> - send the network boot firmware using mdio
> - send the linux image using raw ethernet frames
> 
> This driver allows to start and stop the WASP SoC.
> 
> Signed-off-by: Daniel Kestrel <kestrelseventyfour@xxxxxxxxx>
> Tested-by: Timo Dorfner <timo.capa@xxxxxxxxx> # tested on Fritzbox 7490
> ---
>  drivers/remoteproc/Kconfig    |   10 +
>  drivers/remoteproc/Makefile   |    1 +
>  drivers/remoteproc/avm_wasp.c | 1051 +++++++++++++++++++++++++++++++++
>  3 files changed, 1062 insertions(+)
>  create mode 100644 drivers/remoteproc/avm_wasp.c
> 
> diff --git a/drivers/remoteproc/Kconfig b/drivers/remoteproc/Kconfig
> index 166019786653..a761186c5171 100644
> --- a/drivers/remoteproc/Kconfig
> +++ b/drivers/remoteproc/Kconfig
> @@ -23,6 +23,16 @@ config REMOTEPROC_CDEV
>  
>  	  It's safe to say N if you don't want to use this interface.
>  
> +config AVM_WASP_REMOTEPROC
> +	tristate "AVM WASP remoteproc support"
> +	depends on NET_DSA_LANTIQ_GSWIP
> +	help
> +	  Say y here to support booting the secondary SoC ATH79 target
> +	  called Wireless Assistant Support Processor (WASP) that some
> +	  AVM Fritzbox devices (3390, 3490, 5490, 5491, 7490) have built in.
> +
> +	  It's safe to say N here.
> +
>  config IMX_REMOTEPROC
>  	tristate "i.MX remoteproc support"
>  	depends on ARCH_MXC
> diff --git a/drivers/remoteproc/Makefile b/drivers/remoteproc/Makefile
> index 5478c7cb9e07..0ae175c6722f 100644
> --- a/drivers/remoteproc/Makefile
> +++ b/drivers/remoteproc/Makefile
> @@ -11,6 +11,7 @@ remoteproc-y				+= remoteproc_sysfs.o
>  remoteproc-y				+= remoteproc_virtio.o
>  remoteproc-y				+= remoteproc_elf_loader.o
>  obj-$(CONFIG_REMOTEPROC_CDEV)		+= remoteproc_cdev.o
> +obj-$(CONFIG_AVM_WASP_REMOTEPROC)	+= avm_wasp.o
>  obj-$(CONFIG_IMX_REMOTEPROC)		+= imx_rproc.o
>  obj-$(CONFIG_IMX_DSP_REMOTEPROC)	+= imx_dsp_rproc.o
>  obj-$(CONFIG_INGENIC_VPU_RPROC)		+= ingenic_rproc.o
> diff --git a/drivers/remoteproc/avm_wasp.c b/drivers/remoteproc/avm_wasp.c
> new file mode 100644
> index 000000000000..6eda4db5cf4d
> --- /dev/null
> +++ b/drivers/remoteproc/avm_wasp.c
> @@ -0,0 +1,1051 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * AVM WASP Remote Processor driver
> + *
> + * Copyright (c) 2019-2020 Andreas Böhler
> + * Copyright (c) 2021-2022 Daniel Kestrel
> + *
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of_device.h>
> +#include <linux/of_mdio.h>
> +#include <linux/of_gpio.h>
> +#include <linux/platform_device.h>
> +#include <linux/remoteproc.h>
> +#include <linux/timekeeping.h>
> +#include <net/sock.h>
> +#include <asm-generic/gpio.h>
> +
> +#include "remoteproc_internal.h"
> +
> +#define WASP_CHUNK_SIZE			14
> +#define WASP_ADDR			0x07
> +#define WASP_TIMEOUT_COUNT		1000
> +#define WASP_WAIT_TIMEOUT_COUNT		20
> +#define WASP_CHECK_LEN_DIVBY4_MASK	0x3
> +
> +#define WASP_WAIT_SLEEP_US_LOW		50000
> +#define WASP_WAIT_SLEEP_US		100000
> +#define WASP_POLL_SLEEP_US		200
> +
> +#define WASP_RESP_OK			0x0002
> +#define WASP_RESP_READY_TO_START	0x0202
> +
> +#define WASP_CMD_SET_PARAMS		0x0c01
> +#define WASP_CMD_SET_CHECKSUM_3390	0x0801
> +#define WASP_CMD_SET_CHECKSUM_X490	0x0401
> +#define WASP_CMD_SET_DATA		0x0e01
> +#define WASP_CMD_START_FIRMWARE_3390	0x0201
> +#define WASP_CMD_START_FIRMWARE_X490	0x0001
> +#define WASP_CMD_START_FIRMWARE2_X490	0x0101
> +
> +#define ETH_TYPE_ATH_ECPS_FRAME		0x88bd
> +#define ETH_BUF_SIZE			1056
> +#define ETH_SEND_LOOP_TIMEOUT_SECS	60
> +#define ETH_MAX_DATA_SIZE		1028
> +#define ETH_DATA_SIZE			1024
> +#define ETH_WASP_PACKET_ID		0x1200
> +
> +#define CMD_FIRMWARE_DATA		0x0104
> +#define CMD_START_FIRMWARE		0xd400
> +
> +#define RESP_DISCOVER			0x0000
> +#define RESP_OK				0x0100
> +#define RESP_STARTING			0x0200
> +#define RESP_ERROR			0x0300
> +
> +static const u32 m_load_addr = 0x81a00000;
> +static const u32 m_start_and_exec_addr = 0xbd003000;
> +
> +static const char mac_data[WASP_CHUNK_SIZE] = {0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
> +		0xaa, 0x04, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00};
> +
> +static char wasp_mac[] = {0x00, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa};
> +
> +enum {
> +	MODEL_3390,
> +	MODEL_X490,
> +	MODEL_UNKNOWN
> +} m_model = MODEL_UNKNOWN;
> +
> +struct wasp_packet {
> +	struct ethhdr eh;
> +	struct __packed {
> +		u16	packet_start;
> +		u8	pad_one[5];
> +		u16	command;
> +		u16	response;
> +		u16	counter;
> +		u8	pad_two;
> +	} hdr;
> +	u8	payload[ETH_MAX_DATA_SIZE];
> +} __packed;
> +
> +static char *firmware = "ath9k-eeprom-ahb-18100000.wmac.bin";
> +module_param(firmware, charp, 0444);
> +MODULE_PARM_DESC(firmware,
> +		 "Filename of the ath9k eeprom to be loaded");
> +
> +static char *caldata = "ath10k/cal-pci-0000:00:00.0.bin";
> +module_param(caldata, charp, 0444);
> +MODULE_PARM_DESC(caldata,
> +		 "Filename of the ath10k caldata to be loaded");
> +
> +static char *netboot = "netboot.fw";
> +module_param(netboot, charp, 0444);
> +MODULE_PARM_DESC(netboot,
> +		 "Filename of the network boot firmware for WASP");
> +
> +static char *image = "wasp-image.bin";
> +module_param(image, charp, 0444);
> +MODULE_PARM_DESC(image,
> +		 "Filename of the linux image to be loaded to WASP");
> +
> +/**
> + * struct avm_wasp_rproc - avmwasp remote processor priv
> + * @rproc: rproc handle
> + * @pdev: pointer to platform device
> + * @fw_blob: pointer to load and save any firmware
> + * @linux_blob: pointer to access initramfs image
> + * @mdio_bus: pointer to mii_bus of gswip device for gpio
> + * @power_gpio: store WASP power gpio descriptor
> + * @reset_gpio: store WASP reset gpio descriptor
> + * @loader_port: store name of the port wasp is connected to
> + * @buffer: recv buffer for feedback from WASP
> + * @ifindex: interface index used for WASP communication
> + */
> +struct avm_wasp_rproc {
> +	struct rproc *rproc;
> +	struct platform_device *pdev;
> +	const struct firmware *fw_blob, *linux_blob;
> +	struct mii_bus *mdio_bus;
> +	struct gpio_desc *power_gpio, *reset_gpio;
> +	char *loader_port;
> +	char buffer[ETH_BUF_SIZE];
> +	int ifindex;
> +};
> +
> +/**
> + * avm_wasp_netboot_mdio_write_u32_split() - write 32bit value
> + * @avmwasp: pointer to drivers private avm_wasp_rproc structure
> + * @mdio_reg: register start value for two mdio registers to write to
> + * @value: value to be written to the register
> + *
> + * As the mdio registers are 16bit, this function writes a 32bit value
> + * to two subsequent mdio registers starting with the specified register
> + * for the mdio address that is used for the connection to the WASP SoC
> + *
> + * Return: 0 on Success, -ETIMEDOUT if avm_wasp_netboot_mdio_write fails
> + */
> +static int avm_wasp_netboot_mdio_write_u32_split(struct avm_wasp_rproc *avmwasp,
> +						 u32 mdio_reg, const u32 value)
> +{
> +	struct device *dev = &avmwasp->pdev->dev;
> +	int ret;
> +
> +	ret = mdiobus_write(avmwasp->mdio_bus, WASP_ADDR, mdio_reg,
> +			    ((value & 0xffff0000) >> 16));
> +	if (ret < 0)
> +		goto err;
> +
> +	ret = mdiobus_write(avmwasp->mdio_bus, WASP_ADDR, mdio_reg + 2,
> +			    (value & 0x0000ffff));
> +	if (ret < 0)
> +		goto err;
> +
> +	return 0;
> +err:
> +	dev_err(dev, "mdio split write failed\n");
> +	return ret;
> +}
> +
> +/**
> + * avm_wasp_read_poll_timeout() - wrap read_poll_timeout_macro
> + * @avmwasp: pointer to drivers private avm_wasp_rproc structure
> + * @udelay: microseconds to wait after read
> + * @utimeout: timeout value in microseconds
> + * @checkval: value to be checked against
> + *
> + * This function checks checkval against the WASP mdio status register,
> + * waits udelay before repeating the read and times out after utimeout.
> + * Separate function because every other use of read_poll_timeout makes
> + * the kernel module around half a kB larger.
> + *
> + * Return: 0 when checkval was read or -ETIMEDOUT if not
> + */
> +static int avm_wasp_read_poll_timeout(struct avm_wasp_rproc *avmwasp,
> +				      u32 udelay, u32 utimeout, u32 checkval)
> +{
> +	u32 val;
> +
> +	return read_poll_timeout(mdiobus_read, val,
> +				 (val == checkval), udelay, utimeout, false,
> +				 avmwasp->mdio_bus, WASP_ADDR, 0x0);
> +}
> +
> +/**
> + * avm_wasp_netboot_write_header() - write header to WASP
> + * @avmwasp: pointer to drivers private avm_wasp_rproc structure
> + * @start_exec_addr: address where to load the firmware to on WASP and where
> + * to start it from
> + * @len: length of the network boot firmware
> + *
> + * Writes the header to WASP using mdio to initiate the start of
> + * transferring the network boot firmware to WASP
> + *
> + * Return: 0 on Success, -ETIMEDOUT if writing header failed based on return
> + * code from WASP or the write methods
> + */
> +static int avm_wasp_netboot_write_header(struct avm_wasp_rproc *avmwasp,
> +					 const u32 start_exec_addr,
> +					 const u32 len)
> +{
> +	struct device *dev = &avmwasp->pdev->dev;
> +	int ret;
> +
> +	ret = avm_wasp_netboot_mdio_write_u32_split(avmwasp, 0x2,
> +						    start_exec_addr);
> +	if (ret < 0)
> +		goto err;
> +
> +	ret = avm_wasp_netboot_mdio_write_u32_split(avmwasp, 0x6, len);
> +	if (ret < 0)
> +		goto err;
> +
> +	ret = avm_wasp_netboot_mdio_write_u32_split(avmwasp, 0xA,
> +						    start_exec_addr);
> +	if (ret < 0)
> +		goto err;
> +
> +	ret = mdiobus_write(avmwasp->mdio_bus, WASP_ADDR, 0x0,
> +			    WASP_CMD_SET_PARAMS);
> +	if (ret < 0)
> +		goto err;
> +
> +	ret = avm_wasp_read_poll_timeout(avmwasp, WASP_POLL_SLEEP_US,
> +					 WASP_TIMEOUT_COUNT * WASP_POLL_SLEEP_US,
> +					 WASP_RESP_OK);
> +	if (ret < 0)
> +		goto err;
> +
> +	return 0;
> +err:
> +	dev_err(dev, "mdio write for header failed\n");
> +	return ret;
> +}
> +
> +/**
> + * avm_wasp_netboot_write_checksum() - write checksum to WASP
> + * @avmwasp: pointer to drivers private avm_wasp_rproc structure
> + * @checksum: calculated checksum value to be sent to WASP
> + *
> + * Writes the calculated checksum for the given network boot firmware
> + * to WASP using mdio as the second step
> + *
> + * Return: 0 on Success, -ETIMEDOUT if writing checksum failed based on
> + * return code from WASP or the write methods
> + */
> +static int avm_wasp_netboot_write_checksum(struct avm_wasp_rproc *avmwasp,
> +					   const u32 checksum)
> +{
> +	struct device *dev = &avmwasp->pdev->dev;
> +	int ret;
> +
> +	ret = avm_wasp_netboot_mdio_write_u32_split(avmwasp, 0x2, checksum);
> +	if (ret < 0)
> +		goto err;
> +
> +	if (m_model == MODEL_3390) {
> +		ret = avm_wasp_netboot_mdio_write_u32_split(avmwasp, 0x6, 0x0000);
> +		if (ret < 0)
> +			goto err;
> +
> +		ret = mdiobus_write(avmwasp->mdio_bus, WASP_ADDR, 0x0,
> +				    WASP_CMD_SET_CHECKSUM_3390);
> +	} else if (m_model == MODEL_X490) {
> +		ret = mdiobus_write(avmwasp->mdio_bus, WASP_ADDR, 0x0,
> +				    WASP_CMD_SET_CHECKSUM_X490);
> +	}
> +	if (ret < 0)
> +		goto err;
> +
> +	ret = avm_wasp_read_poll_timeout(avmwasp, WASP_POLL_SLEEP_US,
> +					 WASP_TIMEOUT_COUNT * WASP_POLL_SLEEP_US,
> +					 WASP_RESP_OK);
> +	if (ret < 0)
> +		goto err;
> +
> +	return 0;
> +err:
> +	dev_err(dev, "mdio write for checksum failed\n");
> +	return ret;
> +}
> +
> +/**
> + * avm_wasp_netboot_write_chunk() - write chunk of data to WASP
> + * @avmwasp: pointer to drivers private avm_wasp_rproc structure
> + * @data: pointer to data
> + * @len: length of data (should not exceed 14 bytes)
> + *
> + * Writes up to 14 bytes of data into the 7 16bit mdio registers
> + * to WASP using mdio
> + *
> + * Return: 0 on Success, -EFAULT if data length is more than 14 bytes or
> + * -ETIMEDOUT if writing the data failed based on return code from WASP
> + * or the write methods
> + */
> +static int avm_wasp_netboot_write_chunk(struct avm_wasp_rproc *avmwasp,
> +					const char *data, size_t len)
> +{
> +	struct device *dev = &avmwasp->pdev->dev;
> +	int i, ret = 0;
> +
> +	if (len > WASP_CHUNK_SIZE || !data)
> +		return -EFAULT;
> +
> +	for (i = 0; i < len && ret >= 0; i += 2)
> +		ret = mdiobus_write(avmwasp->mdio_bus, WASP_ADDR, i + 2,
> +				    *((u16 *)(data + i)));
> +	if (ret < 0)
> +		goto err;
> +
> +	ret = mdiobus_write(avmwasp->mdio_bus, WASP_ADDR, 0x0, WASP_CMD_SET_DATA);
> +	if (ret < 0)
> +		goto err;
> +
> +	ret = avm_wasp_read_poll_timeout(avmwasp, WASP_POLL_SLEEP_US,
> +					 WASP_TIMEOUT_COUNT * WASP_POLL_SLEEP_US,
> +					 WASP_RESP_OK);
> +	if (ret < 0)
> +		goto err;
> +
> +	return 0;
> +err:
> +	dev_err(dev, "mdio write for data chunk failed\n");
> +	return ret;
> +}
> +
> +/**
> + * avm_wasp_netboot_calc_checksum() - calculate netboot firmware checksum
> + * @avmwasp: pointer to drivers private avm_wasp_rproc structure
> + *
> + * Calculates the checksum by using the firmware in fw_blob from the private
> + * avm_wasp_rproc structure
> + *
> + * Return: Calculated checksum
> + */
> +static u32 avm_wasp_netboot_calc_checksum(struct avm_wasp_rproc *avmwasp)
> +{
> +	u32 checksum = U32_MAX, count = U32_MAX, cs;
> +	const u8 *p_firmware, *p_firmware_end;
> +
> +	p_firmware = avmwasp->fw_blob->data;
> +	p_firmware_end = p_firmware + avmwasp->fw_blob->size;
> +
> +	while (p_firmware < p_firmware_end) {
> +		cs = be32_to_cpu(*(u32 *)p_firmware);
> +		checksum = checksum - cs;
> +		count++;
> +		p_firmware += 4;
> +	}
> +
> +	checksum = checksum - count;
> +	return checksum;
> +}
> +
> +/**
> + * avm_wasp_netboot_load_firmware() - load netboot firmware to WASP
> + * @avmwasp: pointer to drivers private avm_wasp_rproc structure
> + *
> + * First the status is checked if poweron and reset were successful.
> + * Implements the process to send header, checksum and the firmware
> + * blob in 14 byte chunks to the WASP processor using mdio
> + * Includes checks between the steps and sending commands to start
> + * the network boot firmware
> + *
> + * Return: 0 on Success, -ENODEV if WASP not ready after reset,
> + * -ETIMEDOUT if there was a timeout on polling or
> + * -EFAULT if other errors have occurred
> + */
> +static int avm_wasp_netboot_load_firmware(struct avm_wasp_rproc *avmwasp)
> +{
> +	struct device *dev = &avmwasp->pdev->dev;
> +	struct mii_bus *mdio_bus = avmwasp->mdio_bus;
> +	const u8 *p_firmware, *p_firmware_end;
> +	int regval, regval2, ret, cont = 1;
> +	u32 checksum, left;
> +
> +	ret = avm_wasp_read_poll_timeout(avmwasp, WASP_WAIT_SLEEP_US,
> +					 WASP_WAIT_TIMEOUT_COUNT *
> +					 WASP_WAIT_SLEEP_US, WASP_RESP_OK);
> +	if (ret) {
> +		dev_err(dev, "error WASP processor not in ready status\n");
> +		return -ENODEV;
> +	}
> +
> +	p_firmware = avmwasp->fw_blob->data;
> +	p_firmware_end = p_firmware + avmwasp->fw_blob->size;
> +
> +	if ((avmwasp->fw_blob->size & WASP_CHECK_LEN_DIVBY4_MASK) ||
> +	    avmwasp->fw_blob->size > U16_MAX) {
> +		dev_err(dev, "error network boot firmware size\n");
> +		return -EFAULT;
> +	}
> +
> +	ret = avm_wasp_netboot_write_header(avmwasp, m_start_and_exec_addr,
> +					    avmwasp->fw_blob->size);
> +	if (ret < 0)
> +		return ret;
> +
> +	checksum = avm_wasp_netboot_calc_checksum(avmwasp);
> +	ret = avm_wasp_netboot_write_checksum(avmwasp, checksum);
> +
> +	if (ret < 0)
> +		return ret;
> +
> +	while (p_firmware < p_firmware_end) {
> +		left = p_firmware_end - p_firmware;
> +		if (left > WASP_CHUNK_SIZE)
> +			left = WASP_CHUNK_SIZE;
> +		ret = avm_wasp_netboot_write_chunk(avmwasp, p_firmware, left);
> +		if (ret < 0)
> +			return ret;
> +
> +		p_firmware += left;
> +	}
> +
> +	usleep_range(WASP_WAIT_SLEEP_US_LOW, WASP_WAIT_SLEEP_US);
> +
> +	if (m_model == MODEL_3390)
> +		ret = mdiobus_write(mdio_bus, WASP_ADDR, 0x0,
> +				    WASP_CMD_START_FIRMWARE_3390);
> +	else if (m_model == MODEL_X490)
> +		ret = mdiobus_write(mdio_bus, WASP_ADDR, 0x0,
> +				    WASP_CMD_START_FIRMWARE_X490);
> +	if (ret < 0) {
> +		dev_err(dev, "writing command failed\n");
> +		return ret;
> +	}
> +
> +	usleep_range(WASP_WAIT_SLEEP_US_LOW, WASP_WAIT_SLEEP_US);
> +
> +	ret = avm_wasp_read_poll_timeout(avmwasp, WASP_WAIT_SLEEP_US,
> +					 WASP_WAIT_TIMEOUT_COUNT *
> +					 WASP_WAIT_SLEEP_US,
> +					 WASP_RESP_READY_TO_START);
> +	if (ret) {
> +		dev_err(dev, "timed out waiting for WASP ready to start\n");
> +		return ret;
> +	}
> +
> +	if (m_model == MODEL_3390)
> +		ret = mdiobus_write(mdio_bus, WASP_ADDR, 0x0,
> +				    WASP_CMD_START_FIRMWARE_3390);
> +	else if (m_model == MODEL_X490)
> +		ret = mdiobus_write(mdio_bus, WASP_ADDR, 0x0,
> +				    WASP_CMD_SET_CHECKSUM_X490);
> +	if (ret < 0) {
> +		dev_err(dev, "writing command failed\n");
> +		return ret;
> +	}
> +
> +	usleep_range(WASP_WAIT_SLEEP_US_LOW, WASP_WAIT_SLEEP_US);
> +
> +	if (m_model == MODEL_3390) {
> +		ret = avm_wasp_read_poll_timeout(avmwasp, WASP_WAIT_SLEEP_US,
> +						 WASP_WAIT_TIMEOUT_COUNT *
> +						 WASP_WAIT_SLEEP_US * 10,
> +						 WASP_RESP_OK);
> +		if (ret) {
> +			dev_err(dev, "timed out waiting for WASP OK\n");
> +			return ret;
> +		}
> +		if (avm_wasp_netboot_write_chunk(avmwasp, mac_data,
> +						 ARRAY_SIZE(mac_data)) < 0) {
> +			dev_err(dev, "error sending MAC address\n");
> +			return -EFAULT;
> +		}
> +	} else if (m_model == MODEL_X490) {
> +		while (cont) {
> +			ret = avm_wasp_read_poll_timeout(avmwasp,
> +							 WASP_WAIT_SLEEP_US,
> +							 WASP_WAIT_TIMEOUT_COUNT *
> +							 WASP_WAIT_SLEEP_US,
> +							 WASP_RESP_OK);
> +			if (ret) {
> +				dev_err(dev,
> +					"timed out waiting for WASP OK\n");
> +				return ret;
> +			}
> +			regval = mdiobus_read(mdio_bus, WASP_ADDR, 0x2);
> +			if (regval < 0) {
> +				dev_err(dev, "mdio read failed\n");
> +				return ret;
> +			}
> +			regval2 = mdiobus_read(mdio_bus, WASP_ADDR, 0x4);
> +			if (regval2 < 0) {
> +				dev_err(dev, "mdio read failed\n");
> +				return ret;
> +			}
> +			ret = mdiobus_write(mdio_bus, WASP_ADDR, 0x0,
> +					    WASP_CMD_SET_CHECKSUM_X490);
> +			if (ret < 0) {
> +				dev_err(dev, "writing command failed\n");
> +				return ret;
> +			}
> +
> +			if (regval == 0 && regval2 != 0)
> +				cont = regval2;
> +			else
> +				cont--;
> +		}
> +
> +		ret = avm_wasp_read_poll_timeout(avmwasp,
> +						 WASP_POLL_SLEEP_US,
> +						 WASP_TIMEOUT_COUNT *
> +						 WASP_POLL_SLEEP_US,
> +						 WASP_RESP_OK);
> +		if (ret) {
> +			dev_err(dev,
> +				"error waiting for checksum OK response\n");
> +			return ret;
> +		}
> +
> +		ret = mdiobus_write(mdio_bus, WASP_ADDR, 0x2, 0x00);
> +		if (ret < 0) {
> +			dev_err(dev, "mdio write failed\n");
> +			return ret;
> +		}
> +		ret = mdiobus_write(mdio_bus, WASP_ADDR, 0x0,
> +				    WASP_CMD_START_FIRMWARE2_X490);
> +		if (ret < 0) {
> +			dev_err(dev, "writing command failed\n");
> +			return ret;
> +		}
> +
> +		regval = mdiobus_read(mdio_bus, WASP_ADDR, 0x0);
> +		if (regval != WASP_RESP_OK) {
> +			dev_err(dev,
> +				"error starting WASP network boot: 0x%x\n",
> +				regval);
> +			return -EFAULT;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +/**
> + * avm_wasp_load_initramfs_image() - load initramfs image to WASP
> + * @avmwasp: pointer to drivers private avm_wasp_rproc structure
> + *
> + * Uses the lan port specified from DT to load the initramfs to
> + * WASP after the network boot firmware was successfully started.
> + * Communication is done by using raw sockets.
> + * There are several commands and status values which are checked.
> + * First a discovery packet is received and then each data packet
> + * is acknowledged by the WASP network boot firmware.
> + * First packet needs to prepend the load address and last packet
> + * needs to append the execution address.
> + *
> + * Return: 0 on Success, -EFAULT if errors with the WASP send protocol
> + * have occurred or if no firmware is found, -EAGAIN if the wasp network
> + * interface is down or the error returned from the failed operating
> + * system function or service
> + */
> +static int avm_wasp_load_initramfs_image(struct avm_wasp_rproc *avmwasp)
> +{
> +	bool done = false;
> +	int ret;
> +	u32 num_chunks = 0, chunk_counter = 0;
> +	short interface_flags;
> +	const u8 *p_firmware, *p_firmware_end;
> +	struct device *dev = &avmwasp->pdev->dev;
> +	struct kvec socket_kvec;
> +	struct msghdr socket_msghdr;
> +	struct net_device *send_netdev;
> +	struct sockaddr send_sock_addr;
> +	struct sockaddr_ll send_socket_address;
> +	struct socket *wasp_socket;
> +	struct wasp_packet *packet = (struct wasp_packet *)
> +			(avmwasp->buffer);
> +	struct __kernel_old_timeval timeout;
> +	time64_t start_time, current_time;
> +
> +	if (!avmwasp->linux_blob) {
> +		dev_err(dev, "error accessing initramfs image\n");
> +		ret = -EFAULT;
> +		goto err;
> +	}
> +
> +	p_firmware = avmwasp->linux_blob->data;
> +	p_firmware_end = p_firmware + avmwasp->linux_blob->size;
> +
> +	ret = sock_create_kern(&init_net, PF_PACKET, SOCK_RAW,
> +			       htons(ETH_TYPE_ATH_ECPS_FRAME),
> +			       &wasp_socket);
> +	if (ret < 0) {
> +		dev_err(dev, "error opening recv socket: %d\n", ret);
> +		goto err;
> +	}
> +
> +	timeout.tv_sec = 10;
> +	timeout.tv_usec = 0;
> +	ret = sock_setsockopt(wasp_socket, SOL_SOCKET, SO_RCVTIMEO_OLD,
> +			      KERNEL_SOCKPTR(&timeout), sizeof(timeout));
> +	if (ret < 0) {
> +		dev_err(dev, "error SO_RCVTIMEO recv socket: %d\n", ret);
> +		goto err_socket;
> +	}
> +
> +	ret = sock_setsockopt(wasp_socket, SOL_SOCKET, SO_SNDTIMEO_OLD,
> +			      KERNEL_SOCKPTR(&timeout), sizeof(timeout));
> +	if (ret < 0) {
> +		dev_err(dev, "error SO_SNDTIMEO send socket: %d\n", ret);
> +		goto err_socket;
> +	}
> +
> +	rcu_read_lock();
> +	send_netdev = dev_get_by_name_rcu(sock_net(wasp_socket->sk),
> +					  avmwasp->loader_port);
> +	if (send_netdev)
> +		interface_flags = (short)dev_get_flags(send_netdev);
> +	rcu_read_unlock();
> +
> +	if (IS_ERR_OR_NULL(send_netdev)) {
> +		dev_err(dev, "error accessing net device\n");
> +		ret = -ENODEV;
> +		goto err_socket;
> +	}
> +
> +	if (!(interface_flags & IFF_UP && interface_flags & IFF_RUNNING)) {
> +		dev_err(dev, "error wasp interface %s is down\n",
> +			avmwasp->loader_port);
> +		ret = -EAGAIN;
> +		goto err_socket;
> +	}
> +
> +	avmwasp->ifindex = send_netdev->ifindex;
> +	ret = dev_get_mac_address(&send_sock_addr, &init_net,
> +				  avmwasp->loader_port);
> +	if (ret < 0) {
> +		dev_err(dev, "error getting mac address: %d\n", ret);
> +		goto err_socket;
> +	}
> +
> +	send_socket_address.sll_halen = ETH_ALEN;
> +	send_socket_address.sll_ifindex = avmwasp->ifindex;
> +	memset(&socket_msghdr, 0, sizeof(socket_msghdr));
> +	socket_msghdr.msg_name = (struct sockaddr *)&send_socket_address;
> +	socket_msghdr.msg_namelen = sizeof(struct sockaddr_ll);
> +
> +	start_time = ktime_get_seconds();
> +
> +	while (!done) {
> +		current_time = ktime_get_seconds();
> +		if ((current_time - start_time) > ETH_SEND_LOOP_TIMEOUT_SECS) {
> +			dev_err(dev,
> +				"waiting for packet from WASP timed out\n");
> +			ret = -ETIMEDOUT;
> +			goto err_socket;
> +		}
> +
> +		socket_kvec.iov_base = avmwasp->buffer;
> +		socket_kvec.iov_len = ETH_BUF_SIZE;
> +		ret = kernel_recvmsg(wasp_socket,
> +				     &socket_msghdr, &socket_kvec, 1,
> +				     ETH_BUF_SIZE, 0);
> +
> +		if (ret < 0) {
> +			dev_err(dev,
> +				"error receiving any packet or timeout: %d\n",
> +				ret);
> +			goto err_socket;
> +		}
> +
> +		if (ret < (sizeof(struct ethhdr) + sizeof(packet->hdr))) {
> +			dev_err(dev,
> +				"packet too small, discard and continue\n");
> +			continue;
> +		}
> +
> +		if (packet->eh.h_proto != ETH_TYPE_ATH_ECPS_FRAME)
> +			continue;
> +
> +		memcpy(wasp_mac, packet->eh.h_source, sizeof(wasp_mac));
> +
> +		if (packet->hdr.packet_start == ETH_WASP_PACKET_ID) {
> +			switch (packet->hdr.response) {
> +			case RESP_DISCOVER:
> +				chunk_counter = 1;
> +				num_chunks = DIV_ROUND_UP(avmwasp->linux_blob->size,
> +							  ETH_DATA_SIZE);
> +				fallthrough;
> +			case RESP_OK:
> +				memcpy(packet->eh.h_dest, wasp_mac, sizeof(packet->eh.h_dest));
> +				packet->eh.h_proto = ETH_TYPE_ATH_ECPS_FRAME;
> +				memcpy(packet->eh.h_source, send_sock_addr.sa_data,
> +				       sizeof(packet->eh.h_source));
> +
> +				if (p_firmware < p_firmware_end) {
> +					size_t bytestosend, send_len;
> +					u32 data_offset = 0;
> +
> +					if (chunk_counter == 1) {
> +						memcpy(packet->payload,
> +						       &m_load_addr,
> +						       sizeof(m_load_addr));
> +						data_offset = sizeof(m_load_addr);
> +					}
> +
> +					if ((p_firmware_end - p_firmware) >=
> +					     ETH_DATA_SIZE)
> +						bytestosend = ETH_DATA_SIZE;
> +					else
> +						bytestosend = p_firmware_end -
> +									p_firmware;
> +					memcpy(&packet->payload[data_offset],
> +					       p_firmware, bytestosend);
> +					p_firmware = p_firmware + ETH_DATA_SIZE;
> +
> +					packet->hdr.packet_start =
> +							ETH_WASP_PACKET_ID;
> +					if (chunk_counter == num_chunks) {
> +						packet->hdr.response =
> +							CMD_START_FIRMWARE;
> +						memcpy(&packet->payload
> +						       [data_offset + bytestosend],
> +						       &m_load_addr,
> +						       sizeof(m_load_addr));
> +						bytestosend += sizeof(m_load_addr);
> +					} else {
> +						packet->hdr.command =
> +							CMD_FIRMWARE_DATA;
> +					}
> +					packet->hdr.counter =
> +							(chunk_counter - 1) * 4;
> +
> +					send_len = sizeof(struct ethhdr)
> +						+ sizeof(packet->hdr) + bytestosend +
> +						data_offset;
> +
> +					socket_kvec.iov_len = send_len;
> +					socket_kvec.iov_base = avmwasp->buffer;
> +
> +					ret = kernel_sendmsg(wasp_socket,
> +							     &socket_msghdr,
> +							     &socket_kvec,
> +							     1, send_len);
> +					if (ret < 0) {
> +						dev_err(dev,
> +							"error sending to WASP %d\n",
> +							ret);
> +						goto err_socket;
> +					}
> +
> +					chunk_counter++;
> +				}
> +				break;
> +			case RESP_ERROR:
> +				dev_err(dev,
> +					"received an WASP error packet\n");
> +				ret = -EFAULT;
> +				goto err_socket;
> +			case RESP_STARTING:
> +				done = true;
> +				ret = 0;
> +				continue;
> +				break;
> +			default:
> +				dev_err(dev, "unknown packet, continue\n");
> +				continue;
> +				break;
> +			}
> +		}
> +	}
> +
> +err_socket:
> +	wasp_socket->ops->release(wasp_socket);
> +err:
> +	return ret;
> +}
> +
> +/**
> + * avm_wasp_rproc_start() - start the remote processor
> + * @rproc: pointer to the rproc structure
> + *
> + * Starts the remote processor by initiating the reset process using
> + * the reset_gpio.
> + * As the first step, the network boot firmware is tried to be loaded
> + * and started.
> + * As a second step, the initramfs image is tried to be loaded
> + * and started.
> + *
> + * Return: 0 on Success, -ENODEV or return code from the called function
> + * if any other error occurred in the process of starting and loading
> + * the firmware files to the WASP processor
> + */
> +static int avm_wasp_rproc_start(struct rproc *rproc)
> +{
> +	struct avm_wasp_rproc *avmwasp = rproc->priv;
> +	struct device *dev = &avmwasp->pdev->dev;
> +	u32 pval;
> +	int ret;
> +
> +	gpiod_set_value(avmwasp->reset_gpio, 0);
> +	usleep_range(WASP_WAIT_SLEEP_US_LOW, WASP_WAIT_SLEEP_US);
> +	gpiod_set_value(avmwasp->reset_gpio, 1);
> +	usleep_range(WASP_WAIT_SLEEP_US_LOW, WASP_WAIT_SLEEP_US);
> +
> +	ret = request_firmware_direct((const struct firmware **)
> +				      &avmwasp->fw_blob, netboot, dev);
> +	if (ret) {
> +		dev_err(dev, "could not load network boot firmware\n");
> +		goto err;
> +	}
> +
> +	ret = of_property_read_u32(dev->of_node, "mdio-device", &pval);
> +	if (ret) {
> +		dev_err(dev, "no mdio-device given\n");
> +		goto err_release_fw;
> +	} else {
> +		struct device_node *mdio_node =
> +					of_find_node_by_phandle(pval);
> +
> +		if (!mdio_node) {
> +			dev_err(dev, "get mdio-device failed\n");
> +			ret = -ENODEV;
> +			goto err_release_fw;
> +		} else {
> +			avmwasp->mdio_bus = of_mdio_find_bus(mdio_node);
> +			of_node_put(mdio_node);
> +			if (!avmwasp->mdio_bus) {
> +				dev_err(dev, "mdio bus not found\n");
> +				ret = -ENODEV;
> +				goto err_release_fw;
> +			}
> +		}
> +	}
> +
> +	ret = avm_wasp_netboot_load_firmware(avmwasp);
> +	if (ret)
> +		goto err_put_device;
> +
> +	ret = avm_wasp_load_initramfs_image(avmwasp);
> +
> +err_put_device:
> +	put_device(&avmwasp->mdio_bus->dev);
> +err_release_fw:
> +	release_firmware(avmwasp->fw_blob);
> +err:
> +	return ret;
> +}
> +
> +/**
> + * avm_wasp_rproc_stop() - stop the remote processor
> + * @rproc: pointer to the rproc structure
> + *
> + * To stop the remote processor the reset gpio is used
> + *
> + * Return: 0 on Success
> + */
> +static int avm_wasp_rproc_stop(struct rproc *rproc)
> +{
> +	struct avm_wasp_rproc *avmwasp = rproc->priv;
> +
> +	gpiod_set_value(avmwasp->reset_gpio, 0);
> +	usleep_range(WASP_WAIT_SLEEP_US_LOW, WASP_WAIT_SLEEP_US);
> +	gpiod_set_value(avmwasp->reset_gpio, 1);
> +	usleep_range(WASP_WAIT_SLEEP_US_LOW, WASP_WAIT_SLEEP_US);
> +
> +	return 0;
> +}
> +
> +/**
> + * avm_wasp_rproc_load() - noop to avoid the ELF binary defaults
> + * @rproc: pointer to the rproc structure
> + * @fw: pointer to firmware struct
> + *
> + * If a load function is not defined in the rproc_ops, then all the settings
> + * like checking the firmware binary will default to ELF checks, which fail
> + * in case of the bootable and compressed initramfs image for WASP.
> + * This function stores the initramfs image that is loaded by the remote
> + * processor framework during boot process into the priv for access by
> + * the initramfs load function avm_wasp_load_initramfs_image().
> + *
> + * Return: Always 0
> + */
> +static int avm_wasp_rproc_load(struct rproc *rproc, const struct firmware *fw)
> +{
> +	struct avm_wasp_rproc *avmwasp = rproc->priv;
> +
> +	avmwasp->linux_blob = fw;
> +
> +	return 0;
> +}
> +
> +static const struct rproc_ops avm_wasp_rproc_ops = {
> +	.start		= avm_wasp_rproc_start,
> +	.stop		= avm_wasp_rproc_stop,
> +	.load		= avm_wasp_rproc_load,
> +};
> +
> +static int avm_wasp_rproc_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct avm_wasp_rproc *avmwasp;
> +	struct rproc *rproc;
> +	struct net_device *netdev;
> +	int ret;
> +	short interface_flags;
> +	const u32 *match_data;
> +	u32 pval;

I normally don't fuss to much about those thing but since it is a new file might
as well enact a clean reverse xmass tree.  Here and throughout the file.

> +
> +	match_data = of_device_get_match_data(dev);
> +	if (IS_ERR_OR_NULL(match_data)) {

Can @match_data can really carry an error message?  As far as I can see
of_device_get_match_data() return either NULL or valid data.

> +		dev_err_probe(dev, PTR_ERR(match_data),
> +			      "model specific data is not defined\n");
> +		ret = -ENODEV;
> +		goto err;
> +	}
> +	m_model = *match_data;
> +
> +	rproc = devm_rproc_alloc(dev, "avm,wasp", &avm_wasp_rproc_ops,
> +				 image, sizeof(*avmwasp));
> +	if (!rproc) {
> +		ret = -ENOMEM;
> +		goto err;
> +	}
> +
> +	rproc->auto_boot = true;
> +
> +	avmwasp = rproc->priv;
> +	avmwasp->rproc = rproc;
> +	avmwasp->pdev = pdev;
> +
> +	ret = request_firmware((const struct firmware **)&avmwasp->fw_blob,
> +			       firmware, dev);
> +	if (ret)
> +		dev_err(dev, "could not load ath9k firmware\n");
> +	release_firmware(avmwasp->fw_blob);

This is puzzling... Whey request a firmware image only to free it right after?

> +
> +	if (m_model == MODEL_X490) {
> +		ret = request_firmware((const struct firmware **)
> +				       &avmwasp->fw_blob, caldata, dev);
> +		if (ret)
> +			dev_err(dev, "could not load ath10k caldata\n");
> +		release_firmware(avmwasp->fw_blob);
> +	}

Same here.

> +
> +	ret = of_property_read_u32(dev->of_node, "link-interface",
> +				   &pval);
> +	if (ret) {
> +		dev_err(dev, "no wasp-port given\n");
> +		goto err;
> +	} else {

There is no need for an @else statement here since the code branches to @err in
case of error.

> +		struct device_node *child = of_find_node_by_phandle(pval);
> +
> +		if (!child) {
> +			dev_err(dev, "get link-interface node failed\n");
> +			ret = -ENODEV;
> +			goto err;
> +		} else {
> +			ret = of_property_read_string(child, "label",
> +						      (const char **)
> +						      &avmwasp->loader_port);
> +			of_node_put(child);
> +			if (ret) {
> +				dev_err(dev, "get link-interface label failed\n");
> +				goto err;
> +			}
> +		}
> +	}
> +
> +	rcu_read_lock();
> +	netdev = dev_get_by_name_rcu(&init_net, avmwasp->loader_port);

Please add the proper header file for all functions that you are using.

> +	if (netdev)
> +		interface_flags = (short)dev_get_flags(netdev);

Any reason for @interface_flags to be a short instead of an unsigned int?

> +	rcu_read_unlock();
> +
> +	if (IS_ERR_OR_NULL(netdev)) {

The documentation for dev_err_probe() indicate NULL is returned in case of
error.  And that is already checked above so no need to do so twice.

> +		dev_err_probe(dev, PTR_ERR(netdev),
> +			      "error accessing net device\n");
> +		ret = -ENODEV;
> +		goto err;
> +	}
> +
> +	if (!(interface_flags & IFF_UP && interface_flags & IFF_RUNNING)) {
> +		dev_err(dev, "error link-interface %s down\n",
> +			avmwasp->loader_port);
> +		ret = -EPROBE_DEFER;
> +		goto err;
> +	}
> +
> +	avmwasp->power_gpio = devm_gpiod_get(dev, "power", GPIOD_OUT_LOW);
> +	if (IS_ERR(avmwasp->power_gpio)) {
> +		ret = dev_err_probe(dev, PTR_ERR(avmwasp->power_gpio),
> +				    "failed to get power gpio\n");
> +		goto err;
> +	}
> +
> +	avmwasp->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
> +	if (IS_ERR(avmwasp->reset_gpio)) {
> +		ret = dev_err_probe(dev, PTR_ERR(avmwasp->reset_gpio),
> +				    "failed to get reset gpio\n");
> +		goto err;
> +	}
> +
> +	platform_set_drvdata(pdev, rproc);
> +
> +	ret = devm_rproc_add(dev, rproc);
> +	if (ret) {
> +		dev_err(dev, "rproc_add failed\n");
> +		goto err;
> +	}
> +
> +	gpiod_set_value(avmwasp->power_gpio, 1);
> +	usleep_range(WASP_WAIT_SLEEP_US_LOW, WASP_WAIT_SLEEP_US);

Why is this needed?  Any kind of delay such as this is sure to raise eyebrows
and as such requires a comment to justify it.

More comments to come tomorrow.

Thanks,
Mathieu

> +
> +err:
> +	return ret;
> +}
> +
> +static int avm_wasp_rproc_remove(struct platform_device *pdev)
> +{
> +	struct rproc *rproc = platform_get_drvdata(pdev);
> +	struct avm_wasp_rproc *avmwasp = rproc->priv;
> +
> +	gpiod_set_value(avmwasp->power_gpio, 0);
> +
> +	return 0;
> +}
> +
> +static const u32 model_3390 = MODEL_3390;
> +static const u32 model_x490 = MODEL_X490;
> +
> +static const struct of_device_id avm_wasp_rproc_of_match[] = {
> +	{ .compatible = "avm,fritzbox3390-wasp", .data = &model_3390 },
> +	{ .compatible = "avm,fritzbox3490-wasp", .data = &model_x490 },
> +	{ .compatible = "avm,fritzbox5490-wasp", .data = &model_x490 },
> +	{ .compatible = "avm,fritzbox5491-wasp", .data = &model_x490 },
> +	{ .compatible = "avm,fritzbox7490-wasp", .data = &model_x490 },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, avm_wasp_rproc_of_match);
> +
> +static struct platform_driver avm_wasp_rproc_driver = {
> +	.probe = avm_wasp_rproc_probe,
> +	.remove = avm_wasp_rproc_remove,
> +	.driver = {
> +		.name = "avm_wasp_rproc",
> +		.of_match_table = avm_wasp_rproc_of_match,
> +	},
> +};
> +
> +module_platform_driver(avm_wasp_rproc_driver);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("AVM WASP remote processor boot driver");
> +MODULE_AUTHOR("Daniel Kestrel <kestrelseventyfour@xxxxxxxxx>");
> -- 
> 2.17.1
> 



[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux