[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