[RFC PATCH 00/16] Introduce ICSSG Ethernet driver

Roger Quadros rogerq at kernel.org
Fri Dec 22 13:43:53 CET 2023


On 22/12/2023 12:26, MD Danish Anwar wrote:
> Hi Roger,
> 
> On 20/12/23 3:29 pm, Roger Quadros wrote:
>> Hi,
>>
>> On 19/12/2023 12:11, MD Danish Anwar wrote:
>>> Introduce ICSSG PRUETH support in uboot. The ICSSG driver is used in TI
>>> AM654 SR2.0.
>>>
>>> The ICSSG PRU Sub-system runs on EMAC firmware. This series Introduces
>>> support for ICSSG driver in uboot. This series also adds the driver's
>>> dependencies.
>>>
>>> The ICSSG2 node is added in device tree overlay so that it remains in
>>> sync with linux kernel.
>>>
>>> The series introduces device tree and config changes and AM65x
>>> to enable ICSSG driver. The series also enables SPL_LOAD_FIT_APPLY_OVERLAY
>>> for AM65x in order to load overlay over spl.
>>>
>>> This series has been tested on AM65x SR2.0, and the ICSSG interface is
>>> able to ping / dhcp and boot kernel using tftp in uboot.
>>>
>>> To use ICSSG2 ethernet, the ICSSG firmware needs to be loaded to PRU RPROC
>>> cores and RPROC cores need to be booted with the firmware. This step is
>>> done inside driver in kernel as kernel supports APIs like
>>> rproc_set_firmware() and rproc_fw_boot(). But as u-boot doesn't have these
>>> APIs, the same needs to be done via u-boot cmds.
>>>
>>> To make sure icssg-eth works we need to do below steps.
>>>
>>> 1. Initialize rproc cores i.e. rproc_init()
>>> 2. Load $firmware_file from partition '1:2' (root) on device (mmc in this
>>>    example)
>>> 3. Load the firmware file to rproc cores passing. i.e. rproc_load()
>>>    taking rproc_id, loadaddr and file size as arguments.
>>> 4. Start rproc cores. i.e. rproc_start() taking rproc_id as arguments
>>>
>>> The above steps are done by running the below commands at u-boot prompt.
>>>
>>> => setenv start_icssg2 'rproc start 14; rproc start 15; rproc start 16; rproc start 17; rproc start 18; rproc start 19'
>>> => setenv stop_icssg2 'rproc stop 14; rproc stop 15; rproc stop 16; rproc stop 17; rproc stop 18; rproc stop 19'
>>> => setenv firmware_dir '/lib/firmware/ti-pruss'
>>> => setenv get_firmware_mmc 'load mmc ${bootpart} ${loadaddr} ${firmware_dir}/${firmware_file}'
>>>
>>> => setenv init_icssg2 'setenv ethact icssg2-eth; setenv autoload no; rproc init; setenv loadaddr 0x80000000; \
>>>     setenv firmware_file am65x-sr2-pru0-prueth-fw.elf; run get_firmware_mmc;  rproc load 14 0x80000000 ${filesize}; \
>>>     setenv loadaddr 0x89000000; setenv firmware_file am65x-sr2-rtu0-prueth-fw.elf; run get_firmware_mmc; rproc load 15 0x89000000 ${filesize}; \
>>>     setenv loadaddr 0x90000000; setenv firmware_file am65x-sr2-txpru0-prueth-fw.elf; run get_firmware_mmc; rproc load 16 0x90000000 ${filesize}; \
>>>     setenv loadaddr 0x80000000; setenv firmware_file am65x-sr2-pru1-prueth-fw.elf; run get_firmware_mmc; rproc load 17 0x80000000 ${filesize}; \
>>>     setenv loadaddr 0x89000000; setenv firmware_file am65x-sr2-rtu1-prueth-fw.elf; run get_firmware_mmc; rproc load 18 0x89000000 ${filesize}; \
>>>     setenv loadaddr 0x90000000; setenv firmware_file am65x-sr2-txpru1-prueth-fw.elf; run get_firmware_mmc; rproc load 19 0x90000000 ${filesize}; \
>>>     run start_icssg2;'
>>
>> A whole bunch of commands are required to get ethernet functional.
>> This is not at all user friendly and will be a maintenance nightmare.
>> What worries me is tracking the 6 different rproc cores and the 6 different firmware files to start 1 ethernet device.
>> This will get even more interesting when we have to deal with different ICSSG instances on different boards.
>>
>> What is preventing the driver from starting the rproc cores it needs so user doesn't need to care about it?
>> All the necessary information is in the Device tree. At least this is how it is done on Linux.
>>
> 
> I tried removing the need for these commands and implementing them
> inside the driver only. I am able to load the firmware from driver using
> the fs_loader API request_firmware_into_buf(). It requires changes to
> dt. A DT node called fs-loader needs to be added also CONFIG_FS_LOADER
> needs to enabled. In the DT node we need to specify the storage media
> that we are using i.e. mmc, ospi, usb. It's upto user to modify the
> storage media, the driver will take the media from DT and try to laod
> firmware from their.
> 
> For loading firmwares to rproc cores, rproc_load() API is needed. Now
> this API takes rproc_id, loadaddr and firmware_size as arguments.
> loadaddr is fixed for all three pru cores. firmware_size is obtained
> from request_firmware_into_buf() but I couldn't find a way to get the
> rproc_id. For now based on the ICSSG instance and slice number I am
> figuring out the rproc_id and passing it to rproc_load() and
> rproc_start() APIs. Please let me know if you have any other / better
> way of finding rproc_id.
> 
> Below is the entire diff to remove these commands and move their
> functionality to driver. Please have a look and let me know if this is ok.
> 

Good to see you got something working so quickly.
It has some rough edges but nothing that is blocking.

> 
> Save New Duplicate & Edit Just Text Twitter
> diff --git a/arch/arm/dts/k3-am654-base-board.dts
> b/arch/arm/dts/k3-am654-base-board.dts
> index cfbcebfa37..c8da72e49c 100644
> --- a/arch/arm/dts/k3-am654-base-board.dts
> +++ b/arch/arm/dts/k3-am654-base-board.dts
> @@ -16,6 +16,13 @@
>  	chosen {
>  		stdout-path = "serial2:115200n8";
>  		bootargs = "earlycon=ns16550a,mmio32,0x02800000";
> +		firmware-loader = <&fs_loader0>;
> +	};
> +
> +	fs_loader0: fs-loader {
> +		bootph-all;
> +		compatible = "u-boot,fs-loader";
> +		phandlepart = <&sdhci1 2>;
>  	};

This is has 2 issues
1) It will not be accepted in Kernel DT. Maybe it could be done in -u-boot.dtsi file.
2) You cannot assume boot medium is always sdhci1 2

> 
>  	memory at 80000000 {
> diff --git a/configs/am65x_evm_a53_defconfig
> b/configs/am65x_evm_a53_defconfig
> index 2755d7082f..c53e938abb 100644
> --- a/configs/am65x_evm_a53_defconfig
> +++ b/configs/am65x_evm_a53_defconfig
> @@ -112,6 +112,7 @@ CONFIG_DM_I2C_GPIO=y
>  CONFIG_SYS_I2C_OMAP24XX=y
>  CONFIG_DM_MAILBOX=y
>  CONFIG_K3_SEC_PROXY=y
> +CONFIG_FS_LOADER=y
>  CONFIG_SUPPORT_EMMC_BOOT=y
>  CONFIG_MMC_IO_VOLTAGE=y
>  CONFIG_MMC_UHS_SUPPORT=y
> diff --git a/configs/am65x_evm_r5_defconfig b/configs/am65x_evm_r5_defconfig
> index b2f1e721b3..2d19935a41 100644
> --- a/configs/am65x_evm_r5_defconfig
> +++ b/configs/am65x_evm_r5_defconfig
> @@ -98,6 +98,7 @@ CONFIG_I2C_SET_DEFAULT_BUS_NUM=y
>  CONFIG_SYS_I2C_OMAP24XX=y
>  CONFIG_DM_MAILBOX=y
>  CONFIG_K3_SEC_PROXY=y
> +CONFIG_FS_LOADER=y
>  CONFIG_K3_AVS0=y
>  CONFIG_SUPPORT_EMMC_BOOT=y
>  CONFIG_MMC_HS200_SUPPORT=y
> diff --git a/drivers/net/ti/icssg_prueth.c b/drivers/net/ti/icssg_prueth.c
> index 40ad827e49..1c4edeb7b7 100644
> --- a/drivers/net/ti/icssg_prueth.c
> +++ b/drivers/net/ti/icssg_prueth.c
> @@ -227,6 +227,10 @@ static int prueth_start(struct udevice *dev)
>  	void *config;
>  	int ret, i;
> 
> +	ret = icssg_start_pru_cores(dev);
> +	if (ret)
> +		return ret;
> +
>  	/* To differentiate channels for SLICE0 vs SLICE1 */
>  	snprintf(chn_name, sizeof(chn_name), "tx%d-0", priv->slice);
> 
> @@ -355,9 +359,11 @@ static void prueth_stop(struct udevice *dev)
>  	phy_shutdown(priv->phydev);
> 
>  	dma_disable(&priv->dma_tx);
> -	dma_free(&priv->dma_tx);
> -
>  	dma_disable(&priv->dma_rx);
> +
> +	icssg_stop_pru_cores(dev);
> +
> +	dma_free(&priv->dma_tx);
>  	dma_free(&priv->dma_rx);
>  }
> 
> @@ -434,6 +440,181 @@ static const struct soc_attr k3_mdio_soc_data[] = {
>  	{ /* sentinel */ },
>  };
> 
> +struct icssg_firmware_load_address {
> +	u32 pru;
> +	u32 rtu;
> +	u32 txpru;
> +};
> +
> +struct icssg_firmwares {
> +	char *pru;
> +	char *rtu;
> +	char *txpru;
> +};
> +
> +static struct icssg_firmwares icssg_emac_firmwares[] = {
> +	{
> +		.pru = "/lib/firmware/ti-pruss/am65x-sr2-pru0-prueth-fw.elf",
> +		.rtu = "/lib/firmware/ti-pruss/am65x-sr2-rtu0-prueth-fw.elf",
> +		.txpru = "/lib/firmware/ti-pruss/am65x-sr2-txpru0-prueth-fw.elf",
> +	},
> +	{
> +		.pru = "/lib/firmware/ti-pruss/am65x-sr2-pru1-prueth-fw.elf",
> +		.rtu = "/lib/firmware/ti-pruss/am65x-sr2-rtu1-prueth-fw.elf",
> +		.txpru = "/lib/firmware/ti-pruss/am65x-sr2-txpru1-prueth-fw.elf",
> +	}
> +};

This information is contained in the DT.

		firmware-name = "ti-pruss/am65x-sr2-pru0-prueth-fw.elf",
				"ti-pruss/am65x-sr2-rtu0-prueth-fw.elf",
				"ti-pruss/am65x-sr2-txpru0-prueth-fw.elf",
				"ti-pruss/am65x-sr2-pru1-prueth-fw.elf",
				"ti-pruss/am65x-sr2-rtu1-prueth-fw.elf",
				"ti-pruss/am65x-sr2-txpru1-prueth-fw.elf";

You will need to introduce a rproc_set_firmware() API so clients can
set their own firmware names.

> +
> +int load_firmware(char *name_fw, u32 *loadaddr)
> +{
> +	struct udevice *fsdev;
> +	int size = 0;
> +
> +	if (!IS_ENABLED(CONFIG_FS_LOADER))
> +		return -EINVAL;
> +
> +	if (!*loadaddr)
> +		return -EINVAL;
> +
> +	if (!get_fs_loader(&fsdev))
> +		size = request_firmware_into_buf(fsdev, name_fw, (void *)*loadaddr,
> 40524, 0);
> +
> +	return size;
> +}

On Linux rproc_boot() does both loading the firmware and starting the rproc
as that is farely generic.
You should introduce rproc_boot() API so loading is taken care of at rproc driver.
All you need to do is call rproc_set_firmware() before rproc_boot().

> +
> +static int icssg_get_instance(struct udevice *dev)
> +{
> +	if (!strcmp(dev->name, "icssg2-eth"))
> +		return 2;
> +	else if (!strcmp(dev->name, "icssg1-eth"))
> +		return 1;
> +	else if (!strcmp(dev->name, "icssg0-eth"))
> +		return 0;
> +
> +	dev_err(dev, "Invalid icssg instance\n");
> +	return -EINVAL;
> +}
> +
> +static int icssg_get_pru_core_number(struct udevice *dev, int slice)
> +{
> +	int instance, num_r5_cores;
> +
> +	instance = icssg_get_instance(dev);
> +	if (instance < 0)
> +		return instance;
> +
> +	if (IS_ENABLED(CONFIG_REMOTEPROC_TI_K3_R5F))
> +		num_r5_cores = 2;
> +
> +	return num_r5_cores +
> +	       instance * PRU_TYPE_MAX * PRUETH_NUM_MACS +
> +	       slice * PRU_TYPE_MAX;

All this doesn't look right. What we need is the rproc device
that matches the PRU/RTU rprocs that we are interested in.

The DT already has this information

		ti,prus = <&pru2_0>, <&rtu2_0>, <&tx_pru2_0>,
			<&pru2_1>, <&rtu2_1>, <&tx_pru2_1>;


All the current rproc APIs use the below to get rproc device from ID
ret = uclass_get_device_by_seq(UCLASS_REMOTEPROC, id, &dev);

You just need to introduce APIs that takes rproc device directly as argument.

In your driver you can call uclass_get_device_by_phandle_id() to get the
rproc device from the rproc phandle?


> +}
> +
> +int icssg_start_pru_cores(struct udevice *dev)
> +{
> +	struct prueth *prueth = dev_get_priv(dev);
> +	struct icssg_firmwares *firmwares;
> +	u32 pru_fw_loadaddr = 0x80000000;
> +	u32 rtu_fw_loadaddr = 0x89000000;
> +	u32 txpru_fw_loadaddr = 0x90000000;

Please avoid hardcoding. You can use malloc to get a temporary buffer area?

Why do you need 3 different addresses?
Once you do a rproc_load isn't the buffer already copied to rproc's memory
so you can discard it or use it for the other rprocs?

> +	int slice, ret, core_id;
> +
> +	firmwares = icssg_emac_firmwares;
> +	slice = prueth->slice;
> +
> +	core_id = icssg_get_pru_core_number(dev, slice);
> +	if (core_id < 0)
> +		return core_id;
> +
> +	/* Initialize all rproc cores */
> +	rproc_init();
> +
> +	/* Loading PRU firmware to PRU core */
> +	ret = load_firmware(firmwares[slice].pru, &pru_fw_loadaddr);

On Linux, loading the firmware is the responsibility of the rproc driver.
Shouldn't it be the same in u-boot?

> +
> +	if (ret < 0) {
> +		dev_err(dev, "Unable to load_firmware %s at addr 0x%x err %d\n",
> +			firmwares[slice].pru, pru_fw_loadaddr, ret);
> +		return ret;
> +	}
> +
> +	dev_info(dev, "Loaded FW %s successfully at addr 0x%x Size = %d Bytes\n",
> +		firmwares[slice].pru, pru_fw_loadaddr, ret);

dev_dbg() here an at all dev_info().

> +	rproc_load(core_id + PRU_TYPE_PRU, pru_fw_loadaddr, ret);
> +
> +	/* Loading RTU firmware to RTU core */
> +	ret = load_firmware(firmwares[slice].rtu, &rtu_fw_loadaddr);
> +
> +	if (ret < 0) {
> +		dev_err(dev, "Unable to load_firmware %s at addr 0x%x err %d\n",
> +			firmwares[slice].rtu, rtu_fw_loadaddr, ret);
> +		return ret;
> +	}
> +
> +	dev_info(dev, "Loaded FW %s successfully at addr 0x%x Size = %d Bytes\n",
> +		firmwares[slice].rtu, rtu_fw_loadaddr, ret);
> +	rproc_load(core_id + PRU_TYPE_RTU, rtu_fw_loadaddr, ret);
> +
> +	/* Loading TX_PRU firmware to TX_PRU core */
> +	ret = load_firmware(firmwares[slice].txpru, &txpru_fw_loadaddr);
> +
> +	if (ret < 0) {
> +		dev_err(dev, "Unable to load_firmware %s at addr 0x%x err %d\n",
> +			firmwares[slice].txpru, txpru_fw_loadaddr, ret);
> +		return ret;
> +	}
> +
> +	dev_info(dev, "Loaded FW %s successfully at addr 0x%x Size = %d Bytes\n",
> +		firmwares[slice].txpru, txpru_fw_loadaddr, ret);
> +	rproc_load(core_id + PRU_TYPE_TX_PRU, txpru_fw_loadaddr, ret);
> +
> +	ret = rproc_start(core_id + PRU_TYPE_PRU);
> +	if (ret) {
> +		dev_err(dev, "failed to start PRU%d: %d\n", slice, ret);
> +		return ret;
> +	}
> +
> +	ret = rproc_start(core_id + PRU_TYPE_RTU);
> +	if (ret) {
> +		dev_err(dev, "failed to start RTU%d: %d\n", slice, ret);
> +		goto halt_pru;
> +	}
> +
> +	ret = rproc_start(core_id + PRU_TYPE_TX_PRU);
> +	if (ret) {
> +		dev_err(dev, "failed to start TX_PRU%d: %d\n", slice, ret);
> +		goto halt_rtu;
> +	}
> +
> +	return 0;
> +
> +halt_rtu:
> +	rproc_stop(core_id + PRU_TYPE_RTU);
> +
> +halt_pru:
> +	rproc_stop(PRU_TYPE_PRU);
> +	return ret;
> +}
> +
> +int icssg_stop_pru_cores(struct udevice *dev)
> +{
> +	struct prueth *prueth = dev_get_priv(dev);
> +	int slice, core_id;
> +
> +	slice = prueth->slice;
> +
> +	core_id = icssg_get_pru_core_number(dev, slice);
> +	if (core_id < 0)
> +		return core_id;
> +
> +	rproc_stop(core_id + PRU_TYPE_PRU);
> +	rproc_stop(core_id + PRU_TYPE_RTU);
> +	rproc_stop(core_id + PRU_TYPE_TX_PRU);
> +
> +	return 0;
> +}
> +
>  static int prueth_probe(struct udevice *dev)
>  {
>  	ofnode eth_ports_node, eth0_node, eth1_node, eth_node;
> diff --git a/include/linux/pruss_driver.h b/include/linux/pruss_driver.h
> index 25272e850e..f17fe8bf58 100644
> --- a/include/linux/pruss_driver.h
> +++ b/include/linux/pruss_driver.h
> @@ -114,6 +114,21 @@ enum pru_ctable_idx {
>  	PRU_C31,
>  };
> 
> +/**
> + * enum pru_type - PRU core type identifier
> + *
> + * @PRU_TYPE_PRU: Programmable Real-time Unit
> + * @PRU_TYPE_RTU: Auxiliary Programmable Real-Time Unit
> + * @PRU_TYPE_TX_PRU: Transmit Programmable Real-Time Unit
> + * @PRU_TYPE_MAX: just keep this one at the end
> + */
> +enum pru_type {
> +	PRU_TYPE_PRU,
> +	PRU_TYPE_RTU,
> +	PRU_TYPE_TX_PRU,
> +	PRU_TYPE_MAX,
> +};
> +
>  /**
>   * enum pruss_mem - PRUSS memory range identifiers
>   */
> 
> with this diff, user don't need to run any extra commands at u-boot.
> Once u-boot prompt is reached, just running ping / dhcp will suffice.
> 
Great!

<snip>

-- 
cheers,
-roger


More information about the U-Boot mailing list