From patchwork Wed Mar 4 02:41:07 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Joe Hershberger X-Patchwork-Id: 446045 X-Patchwork-Delegate: sjg@chromium.org Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from theia.denx.de (theia.denx.de [85.214.87.163]) by ozlabs.org (Postfix) with ESMTP id 11F7614007D for ; Wed, 4 Mar 2015 13:47:22 +1100 (AEDT) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 47C0A4B691; Wed, 4 Mar 2015 03:45:50 +0100 (CET) Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id hh66DEbR4pGm; Wed, 4 Mar 2015 03:45:50 +0100 (CET) Received: from theia.denx.de (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 0CDABA7420; Wed, 4 Mar 2015 03:44:30 +0100 (CET) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 5EFEB4B61A for ; Wed, 4 Mar 2015 03:43:21 +0100 (CET) Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id WN6ld9AHitZE for ; Wed, 4 Mar 2015 03:43:21 +0100 (CET) X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 NOT_IN_SPAMCOP=-1.5 NOT_IN_BL_NJABL=-1.5 (only DNSBL check requested) Received: from ni.com (skprod3.natinst.com [130.164.80.24]) by theia.denx.de (Postfix) with ESMTPS id 3840A4B616 for ; Wed, 4 Mar 2015 03:43:16 +0100 (CET) Received: from us-aus-mgwout2.amer.corp.natinst.com (nb-chan1-1338.natinst.com [130.164.19.134]) by us-aus-skprod3.natinst.com (8.15.0.59/8.15.0.59) with ESMTP id t242hFqC022999; Tue, 3 Mar 2015 20:43:15 -0600 Received: from linux-xvxi.natinst.com ([130.164.14.198]) by us-aus-mgwout2.amer.corp.natinst.com (Lotus Domino Release 8.5.3FP6) with ESMTP id 2015030320431514-645236 ; Tue, 3 Mar 2015 20:43:15 -0600 From: Joe Hershberger To: u-boot@lists.denx.de Date: Tue, 3 Mar 2015 20:41:07 -0600 Message-Id: <1425436881-10323-14-git-send-email-joe.hershberger@ni.com> X-Mailer: git-send-email 1.7.11.5 In-Reply-To: <1425436881-10323-1-git-send-email-joe.hershberger@ni.com> References: <1424822552-4366-1-git-send-email-joe.hershberger@ni.com> <1425436881-10323-1-git-send-email-joe.hershberger@ni.com> X-MIMETrack: Itemize by SMTP Server on US-AUS-MGWOut2/AUS/H/NIC(Release 8.5.3FP6|November 21, 2013) at 03/03/2015 08:43:15 PM, Serialize by Router on US-AUS-MGWOut2/AUS/H/NIC(Release 8.5.3FP6|November 21, 2013) at 03/03/2015 08:43:15 PM, Serialize complete at 03/03/2015 08:43:15 PM X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10432:, , definitions=2015-03-03_08:, , signatures=0 Cc: Joe Hershberger Subject: [U-Boot] [PATCH v5 13/27] dm: eth: Add basic driver model support to Ethernet stack X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.15 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" First just add support for MAC drivers. Signed-off-by: Joe Hershberger Reviewed-by: Simon Glass --- Changes in v5: -Add a note to doc/README.drivers.eth about its obsolescence -Expanded the Kconfig help -Moved dm/ header -Use local var for priv in eth_get_dev() Changes in v4: --Don't prevent eth_initialize on driver model --If current == NULL, always check if there is a device available in eth_get_dev --Include ethprime handling in eth_initialize --Look up MAC address in post-probe --Move env init call from uclass init to eth_initialize --Print the alias in eth_initialize --Use eth_initialize to probe all devices and write_hwaddr -Add a helper function for eth_uclass_priv -Add documentation to the structures -Add eth_get_ops helper -Change -1 returns to error constants -Change puts to printf -Redo the seq / probe implementation -Remove bd_t *bis from dm eth_ops init function -Remove checks for driver==NULL -Remove priv pointer in per-device priv struct (drivers already get their own directly from DM) -Rename halt() to stop() in ops -Rename init() to start() in ops -Stop handling selecting a new "current" in pre-unbind as it will now work itself out by clearing the pointer Changes in v3: --Fail init if not activated --Fail probe if ethaddr not set -Allow current eth dev to be NULL -Correct failure chaining from bind to probe to init -Correct the pre_unbind logic -Fixed blank line formatting for variable declaration -Update ethaddr from env unconditionally on init -Use set current to select the current device regardless of the previous selection Changes in v2: -Cause an invalid name to fail binding -Changed eth_uclass_priv local var names to be uc_priv -Move the hwaddr to platdata so that its memory is allocated at bind when we need it -Prevent device from being probed before used by a command (i.e. before eth_init()). -Rebase on top of dm/master -Removed extra parentheses -Stop maintaining our own index and use DM seq now that it works for our needs -Update error codes -Updated comments common/cmd_bdinfo.c | 2 + doc/README.drivers.eth | 6 + drivers/net/Kconfig | 9 ++ include/dm/uclass-id.h | 1 + include/net.h | 52 ++++++++ net/eth.c | 346 ++++++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 410 insertions(+), 6 deletions(-) diff --git a/common/cmd_bdinfo.c b/common/cmd_bdinfo.c index aa81da2..b4cce25 100644 --- a/common/cmd_bdinfo.c +++ b/common/cmd_bdinfo.c @@ -34,6 +34,7 @@ static void print_eth(int idx) printf("%-12s= %s\n", name, val); } +#ifndef CONFIG_DM_ETH __maybe_unused static void print_eths(void) { @@ -52,6 +53,7 @@ static void print_eths(void) printf("current eth = %s\n", eth_get_name()); printf("ip_addr = %s\n", getenv("ipaddr")); } +#endif __maybe_unused static void print_lnum(const char *name, unsigned long long value) diff --git a/doc/README.drivers.eth b/doc/README.drivers.eth index eb83038..98728bc 100644 --- a/doc/README.drivers.eth +++ b/doc/README.drivers.eth @@ -1,3 +1,9 @@ +!!! WARNING !!! + +This guide describes to the old way of doing things. No new Ethernet drivers +should be implemented this way. All new drivers should be written against the +U-Boot core driver model. See doc/driver-model/README.txt + ----------------------- Ethernet Driver Guide ----------------------- diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index e69de29..94cf099 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -0,0 +1,9 @@ +config DM_ETH + bool "Enable Driver Model for Ethernet drivers" + depends on DM + help + Enable driver model for Ethernet. + + The eth_*() interface will be implemented by the UC_ETH class + This is currently implemented in net/eth.c + Look in include/net.h for details. diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index 91bb90d..ad96682 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -34,6 +34,7 @@ enum uclass_id { UCLASS_I2C_GENERIC, /* Generic I2C device */ UCLASS_I2C_EEPROM, /* I2C EEPROM device */ UCLASS_MOD_EXP, /* RSA Mod Exp device */ + UCLASS_ETH, /* Ethernet device */ UCLASS_COUNT, UCLASS_INVALID = -1, diff --git a/include/net.h b/include/net.h index 10d38f8..508c572 100644 --- a/include/net.h +++ b/include/net.h @@ -78,6 +78,57 @@ enum eth_state_t { ETH_STATE_ACTIVE }; +#ifdef CONFIG_DM_ETH +/** + * struct eth_pdata - Platform data for Ethernet MAC controllers + * + * @iobase: The base address of the hardware registers + * @enetaddr: The Ethernet MAC address that is loaded from EEPROM or env + */ +struct eth_pdata { + phys_addr_t iobase; + unsigned char enetaddr[6]; +}; + +/** + * struct eth_ops - functions of Ethernet MAC controllers + * + * start: Prepare the hardware to send and receive packets + * send: Send the bytes passed in "packet" as a packet on the wire + * recv: Check if the hardware received a packet. Call the network stack if so + * stop: Stop the hardware from looking for packets - may be called even if + * state == PASSIVE + * mcast: Join or leave a multicast group (for TFTP) - optional + * write_hwaddr: Write a MAC address to the hardware (used to pass it to Linux + * on some platforms like ARM). This function expects the + * eth_pdata::enetaddr field to be populated - optional + * read_rom_hwaddr: Some devices have a backup of the MAC address stored in a + * ROM on the board. This is how the driver should expose it + * to the network stack. This function should fill in the + * eth_pdata::enetaddr field - optional + */ +struct eth_ops { + int (*start)(struct udevice *dev); + int (*send)(struct udevice *dev, void *packet, int length); + int (*recv)(struct udevice *dev); + void (*stop)(struct udevice *dev); +#ifdef CONFIG_MCAST_TFTP + int (*mcast)(struct udevice *dev, const u8 *enetaddr, int join); +#endif + int (*write_hwaddr)(struct udevice *dev); + int (*read_rom_hwaddr)(struct udevice *dev); +}; + +#define eth_get_ops(dev) ((struct eth_ops *)(dev)->driver->ops) + +struct udevice *eth_get_dev(void); /* get the current device */ +unsigned char *eth_get_ethaddr(void); /* get the current device MAC */ +/* Used only when NetConsole is enabled */ +int eth_init_state_only(void); /* Set active state */ +void eth_halt_state_only(void); /* Set passive state */ +#endif + +#ifndef CONFIG_DM_ETH struct eth_device { char name[16]; unsigned char enetaddr[6]; @@ -144,6 +195,7 @@ int eth_write_hwaddr(struct eth_device *dev, const char *base_name, int eth_number); int usb_eth_initialize(bd_t *bi); +#endif int eth_initialize(void); /* Initialize network subsystem */ void eth_try_another(int first_restart); /* Change the device */ diff --git a/net/eth.c b/net/eth.c index 66ecb79..1abf027 100644 --- a/net/eth.c +++ b/net/eth.c @@ -1,16 +1,19 @@ /* - * (C) Copyright 2001-2010 + * (C) Copyright 2001-2015 * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + * Joe Hershberger, National Instruments * * SPDX-License-Identifier: GPL-2.0+ */ #include #include +#include #include #include #include #include +#include DECLARE_GLOBAL_DATA_PTR; @@ -76,6 +79,339 @@ static int eth_mac_skip(int index) static void eth_current_changed(void); +#ifdef CONFIG_DM_ETH +/** + * struct eth_device_priv - private structure for each Ethernet device + * + * @state: The state of the Ethernet MAC driver (defined by enum eth_state_t) + */ +struct eth_device_priv { + enum eth_state_t state; +}; + +/** + * struct eth_uclass_priv - The structure attached to the uclass itself + * + * @current: The Ethernet device that the network functions are using + */ +struct eth_uclass_priv { + struct udevice *current; +}; + +static struct eth_uclass_priv *eth_get_uclass_priv(void) +{ + struct uclass *uc; + + uclass_get(UCLASS_ETH, &uc); + assert(uc); + return uc->priv; +} + +static void eth_set_current_to_next(void) +{ + struct eth_uclass_priv *uc_priv; + + uc_priv = eth_get_uclass_priv(); + if (uc_priv->current) + uclass_next_device(&uc_priv->current); + if (!uc_priv->current) + uclass_first_device(UCLASS_ETH, &uc_priv->current); +} + +struct udevice *eth_get_dev(void) +{ + struct eth_uclass_priv *uc_priv; + + uc_priv = eth_get_uclass_priv(); + if (!uc_priv->current) + uclass_first_device(UCLASS_ETH, + &uc_priv->current); + return uc_priv->current; +} + +static void eth_set_dev(struct udevice *dev) +{ + device_probe(dev); + eth_get_uclass_priv()->current = dev; +} + +unsigned char *eth_get_ethaddr(void) +{ + struct eth_pdata *pdata; + + if (eth_get_dev()) { + pdata = eth_get_dev()->platdata; + return pdata->enetaddr; + } + + return NULL; +} + +/* Set active state without calling start on the driver */ +int eth_init_state_only(void) +{ + struct udevice *current; + struct eth_device_priv *priv; + + current = eth_get_dev(); + if (!current || !device_active(current)) + return -EINVAL; + + priv = current->uclass_priv; + priv->state = ETH_STATE_ACTIVE; + + return 0; +} + +/* Set passive state without calling stop on the driver */ +void eth_halt_state_only(void) +{ + struct udevice *current; + struct eth_device_priv *priv; + + current = eth_get_dev(); + if (!current || !device_active(current)) + return; + + priv = current->uclass_priv; + priv->state = ETH_STATE_PASSIVE; +} + +int eth_get_dev_index(void) +{ + if (eth_get_dev()) + return eth_get_dev()->seq; + return -1; +} + +int eth_init(void) +{ + struct udevice *current; + struct udevice *old_current; + + current = eth_get_dev(); + if (!current) { + printf("No ethernet found.\n"); + return -ENODEV; + } + + old_current = current; + do { + debug("Trying %s\n", current->name); + + if (device_active(current)) { + uchar env_enetaddr[6]; + struct eth_pdata *pdata = current->platdata; + + /* Sync environment with network device */ + if (eth_getenv_enetaddr_by_index("eth", current->seq, + env_enetaddr)) + memcpy(pdata->enetaddr, env_enetaddr, 6); + else + memset(pdata->enetaddr, 0, 6); + + if (eth_get_ops(current)->start(current) >= 0) { + struct eth_device_priv *priv = + current->uclass_priv; + + priv->state = ETH_STATE_ACTIVE; + return 0; + } + } + debug("FAIL\n"); + + /* This will ensure the new "current" attempted to probe */ + eth_try_another(0); + current = eth_get_dev(); + } while (old_current != current); + + return -ENODEV; +} + +void eth_halt(void) +{ + struct udevice *current; + struct eth_device_priv *priv; + + current = eth_get_dev(); + if (!current || !device_active(current)) + return; + + eth_get_ops(current)->stop(current); + priv = current->uclass_priv; + priv->state = ETH_STATE_PASSIVE; +} + +int eth_send(void *packet, int length) +{ + struct udevice *current; + + current = eth_get_dev(); + if (!current) + return -ENODEV; + + if (!device_active(current)) + return -EINVAL; + + return eth_get_ops(current)->send(current, packet, length); +} + +int eth_rx(void) +{ + struct udevice *current; + + current = eth_get_dev(); + if (!current) + return -ENODEV; + + if (!device_active(current)) + return -EINVAL; + + return eth_get_ops(current)->recv(current); +} + +static int eth_write_hwaddr(struct udevice *dev) +{ + struct eth_pdata *pdata = dev->platdata; + int ret = 0; + + if (!dev || !device_active(dev)) + return -EINVAL; + + /* seq is valid since the device is active */ + if (eth_get_ops(dev)->write_hwaddr && !eth_mac_skip(dev->seq)) { + if (!is_valid_ether_addr(pdata->enetaddr)) { + printf("\nError: %s address %pM illegal value\n", + dev->name, pdata->enetaddr); + return -EINVAL; + } + + ret = eth_get_ops(dev)->write_hwaddr(dev); + if (ret) + printf("\nWarning: %s failed to set MAC address\n", + dev->name); + } + + return ret; +} + +int eth_initialize(void) +{ + int num_devices = 0; + struct udevice *dev; + + bootstage_mark(BOOTSTAGE_ID_NET_ETH_START); + eth_env_init(); + + /* + * Devices need to write the hwaddr even if not started so that Linux + * will have access to the hwaddr that u-boot stored for the device. + * This is accomplished by attempting to probe each device and calling + * their write_hwaddr() operation. + */ + uclass_first_device(UCLASS_ETH, &dev); + if (!dev) { + printf("No ethernet found.\n"); + bootstage_error(BOOTSTAGE_ID_NET_ETH_START); + } else { + bootstage_mark(BOOTSTAGE_ID_NET_ETH_INIT); + do { + if (num_devices) + printf(", "); + + printf("eth%d: %s", dev->seq, dev->name); + + eth_write_hwaddr(dev); + + uclass_next_device(&dev); + num_devices++; + } while (dev); + + putc('\n'); + } + + return num_devices; +} + +static int eth_post_bind(struct udevice *dev) +{ + if (strchr(dev->name, ' ')) { + printf("\nError: eth device name \"%s\" has a space!\n", + dev->name); + return -EINVAL; + } + + return 0; +} + +static int eth_pre_unbind(struct udevice *dev) +{ + /* Don't hang onto a pointer that is going away */ + if (dev == eth_get_uclass_priv()->current) + eth_set_dev(NULL); + + return 0; +} + +static int eth_post_probe(struct udevice *dev) +{ + struct eth_device_priv *priv = dev->uclass_priv; + struct eth_pdata *pdata = dev->platdata; + unsigned char env_enetaddr[6]; + + priv->state = ETH_STATE_INIT; + + /* Check if the device has a MAC address in ROM */ + if (eth_get_ops(dev)->read_rom_hwaddr) + eth_get_ops(dev)->read_rom_hwaddr(dev); + + eth_getenv_enetaddr_by_index("eth", dev->seq, env_enetaddr); + if (!is_zero_ether_addr(env_enetaddr)) { + if (!is_zero_ether_addr(pdata->enetaddr) && + memcmp(pdata->enetaddr, env_enetaddr, 6)) { + printf("\nWarning: %s MAC addresses don't match:\n", + dev->name); + printf("Address in SROM is %pM\n", + pdata->enetaddr); + printf("Address in environment is %pM\n", + env_enetaddr); + } + + /* Override the ROM MAC address */ + memcpy(pdata->enetaddr, env_enetaddr, 6); + } else if (is_valid_ether_addr(pdata->enetaddr)) { + eth_setenv_enetaddr_by_index("eth", dev->seq, pdata->enetaddr); + printf("\nWarning: %s using MAC address from ROM\n", + dev->name); + } else if (is_zero_ether_addr(pdata->enetaddr)) { + printf("\nError: %s address not set.\n", + dev->name); + return -EINVAL; + } + + return 0; +} + +static int eth_pre_remove(struct udevice *dev) +{ + eth_get_ops(dev)->stop(dev); + + return 0; +} + +UCLASS_DRIVER(eth) = { + .name = "eth", + .id = UCLASS_ETH, + .post_bind = eth_post_bind, + .pre_unbind = eth_pre_unbind, + .post_probe = eth_post_probe, + .pre_remove = eth_pre_remove, + .priv_auto_alloc_size = sizeof(struct eth_uclass_priv), + .per_device_auto_alloc_size = sizeof(struct eth_device_priv), +}; +#endif + +#ifndef CONFIG_DM_ETH /* * CPU and board-specific Ethernet initializations. Aliased function * signals caller to move on @@ -427,6 +763,7 @@ int eth_rx(void) return eth_current->recv(eth_current); } +#endif /* ifndef CONFIG_DM_ETH */ #ifdef CONFIG_API static void eth_save_packet(void *packet, int length) @@ -490,7 +827,7 @@ static void eth_current_changed(void) void eth_try_another(int first_restart) { - static struct eth_device *first_failed; + static void *first_failed; char *ethrotate; /* @@ -519,12 +856,9 @@ void eth_set_current(void) { static char *act; static int env_changed_id; - struct eth_device *old_current; + void *old_current; int env_id; - if (!eth_get_dev()) /* XXX no current */ - return; - env_id = get_env_id(); if ((act == NULL) || (env_changed_id != env_id)) { act = getenv("ethact");