9
0
Fork 0
barebox/net/eth.c

400 lines
8.1 KiB
C

/*
* (C) Copyright 2001-2004
* Wolfgang Denk, DENX Software Engineering, wd@denx.de.
*
* See file CREDITS for list of people who contributed to this
* project.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <common.h>
#include <command.h>
#include <complete.h>
#include <driver.h>
#include <init.h>
#include <net.h>
#include <of.h>
#include <linux/phy.h>
#include <errno.h>
#include <malloc.h>
static struct eth_device *eth_current;
static uint64_t last_link_check;
static LIST_HEAD(netdev_list);
struct eth_ethaddr {
struct list_head list;
u8 ethaddr[6];
int ethid;
struct device_node *node;
};
static LIST_HEAD(ethaddr_list);
static void register_preset_mac_address(struct eth_device *edev, const char *ethaddr)
{
unsigned char ethaddr_str[sizeof("xx:xx:xx:xx:xx:xx")];
ethaddr_to_string(ethaddr, ethaddr_str);
if (is_valid_ether_addr(ethaddr)) {
dev_info(&edev->dev, "got preset MAC address: %s\n", ethaddr_str);
dev_set_param(&edev->dev, "ethaddr", ethaddr_str);
}
}
static int eth_get_registered_ethaddr(struct eth_device *edev, void *buf)
{
struct eth_ethaddr *addr;
struct device_node *node = NULL;
if (edev->parent)
node = edev->parent->device_node;
list_for_each_entry(addr, &ethaddr_list, list) {
if ((node && node == addr->node) ||
addr->ethid == edev->dev.id) {
memcpy(buf, addr->ethaddr, 6);
return 0;
}
}
return -EINVAL;
}
static void eth_drop_ethaddr(int ethid)
{
struct eth_ethaddr *addr, *tmp;
list_for_each_entry_safe(addr, tmp, &ethaddr_list, list) {
if (addr->ethid == ethid) {
list_del(&addr->list);
free(addr);
return;
}
}
}
void eth_register_ethaddr(int ethid, const char *ethaddr)
{
struct eth_ethaddr *addr;
eth_drop_ethaddr(ethid);
addr = xzalloc(sizeof(*addr));
addr->ethid = ethid;
memcpy(addr->ethaddr, ethaddr, 6);
list_add_tail(&addr->list, &ethaddr_list);
}
static struct eth_device *eth_get_by_node(struct device_node *node)
{
struct eth_device *edev;
list_for_each_entry(edev, &netdev_list, list) {
if (!edev->parent)
continue;
if (!edev->parent->device_node)
continue;
if (edev->parent->device_node == node)
return edev;
}
return NULL;
}
void of_eth_register_ethaddr(struct device_node *node, const char *ethaddr)
{
struct eth_ethaddr *addr;
struct eth_device *edev;
edev = eth_get_by_node(node);
if (edev) {
register_preset_mac_address(edev, ethaddr);
return;
}
addr = xzalloc(sizeof(*addr));
addr->node = node;
memcpy(addr->ethaddr, ethaddr, 6);
list_add_tail(&addr->list, &ethaddr_list);
}
void eth_set_current(struct eth_device *eth)
{
eth_current = eth;
}
struct eth_device * eth_get_current(void)
{
return eth_current;
}
struct eth_device *eth_get_byname(const char *ethname)
{
struct eth_device *edev;
list_for_each_entry(edev, &netdev_list, list) {
if (!strcmp(ethname, dev_name(&edev->dev)))
return edev;
}
return NULL;
}
#ifdef CONFIG_AUTO_COMPLETE
int eth_complete(struct string_list *sl, char *instr)
{
struct eth_device *edev;
const char *devname;
int len;
len = strlen(instr);
list_for_each_entry(edev, &netdev_list, list) {
devname = dev_name(&edev->dev);
if (strncmp(instr, devname, len))
continue;
string_list_add_asprintf(sl, "%s ", devname);
}
return COMPLETE_CONTINUE;
}
#endif
/*
* Check for link if we haven't done so for longer.
*/
static int eth_carrier_check(struct eth_device *edev, int force)
{
int ret;
if (!IS_ENABLED(CONFIG_PHYLIB))
return 0;
if (!edev->phydev)
return 0;
if (force)
phy_wait_aneg_done(edev->phydev);
if (force || is_timeout(last_link_check, 5 * SECOND) ||
!edev->phydev->link) {
ret = phy_update_status(edev->phydev);
if (ret)
return ret;
last_link_check = get_time_ns();
}
return edev->phydev->link ? 0 : -ENETDOWN;
}
/*
* Check if we have a current ethernet device and
* eventually open it if we have to.
*/
static int eth_check_open(struct eth_device *edev)
{
int ret;
if (edev->active)
return 0;
ret = edev->open(eth_current);
if (ret)
return ret;
edev->active = 1;
return eth_carrier_check(edev, 1);
}
int eth_send(struct eth_device *edev, void *packet, int length)
{
int ret;
ret = eth_check_open(edev);
if (ret)
return ret;
ret = eth_carrier_check(edev, 0);
if (ret)
return ret;
led_trigger_network(LED_TRIGGER_NET_TX);
return edev->send(eth_current, packet, length);
}
static int __eth_rx(struct eth_device *edev)
{
int ret;
ret = eth_check_open(edev);
if (ret)
return ret;
ret = eth_carrier_check(edev, 0);
if (ret)
return ret;
return edev->recv(eth_current);
}
int eth_rx(void)
{
struct eth_device *edev;
list_for_each_entry(edev, &netdev_list, list) {
if (edev->active)
__eth_rx(edev);
}
return 0;
}
static int eth_set_ethaddr(struct param_d *param, void *priv)
{
struct eth_device *edev = priv;
edev->set_ethaddr(edev, edev->ethaddr);
return 0;
}
#ifdef CONFIG_OFTREE
static int eth_of_fixup(struct device_node *root, void *unused)
{
struct eth_device *edev;
struct device_node *node;
int ret;
/*
* Add the mac-address property for each network device we
* find a nodepath for and which has a valid mac address.
*/
list_for_each_entry(edev, &netdev_list, list) {
if (!is_valid_ether_addr(edev->ethaddr)) {
dev_dbg(&edev->dev,
"%s: no valid mac address, cannot fixup\n",
__func__);
continue;
}
if (edev->nodepath) {
node = of_find_node_by_path_from(root, edev->nodepath);
} else {
char eth[12];
sprintf(eth, "ethernet%d", edev->dev.id);
node = of_find_node_by_alias(root, eth);
}
if (!node) {
dev_dbg(&edev->dev, "%s: no node to fixup\n", __func__);
continue;
}
ret = of_set_property(node, "mac-address", edev->ethaddr, 6, 1);
if (ret)
pr_err("Setting mac-address property of %s failed with: %s\n",
node->full_name, strerror(-ret));
}
return 0;
}
static int eth_register_of_fixup(void)
{
return of_register_fixup(eth_of_fixup, NULL);
}
late_initcall(eth_register_of_fixup);
#endif
int eth_register(struct eth_device *edev)
{
struct device_d *dev = &edev->dev;
unsigned char ethaddr[6];
int ret, found = 0;
if (!edev->get_ethaddr) {
dev_err(dev, "no get_mac_address found for current eth device\n");
return -1;
}
strcpy(edev->dev.name, "eth");
if (edev->parent)
edev->dev.parent = edev->parent;
if (edev->dev.parent && edev->dev.parent->device_node) {
edev->dev.id = of_alias_get_id(edev->dev.parent->device_node, "ethernet");
if (edev->dev.id < 0)
edev->dev.id = DEVICE_ID_DYNAMIC;
} else {
edev->dev.id = DEVICE_ID_DYNAMIC;
}
ret = register_device(&edev->dev);
if (ret)
return ret;
dev_add_param_ip(dev, "ipaddr", NULL, NULL, &edev->ipaddr, edev);
dev_add_param_ip(dev, "serverip", NULL, NULL, &edev->serverip, edev);
dev_add_param_ip(dev, "gateway", NULL, NULL, &edev->gateway, edev);
dev_add_param_ip(dev, "netmask", NULL, NULL, &edev->netmask, edev);
dev_add_param_mac(dev, "ethaddr", eth_set_ethaddr, NULL, edev->ethaddr, edev);
if (edev->init)
edev->init(edev);
list_add_tail(&edev->list, &netdev_list);
ret = eth_get_registered_ethaddr(edev, ethaddr);
if (!ret)
found = 1;
if (!found) {
ret = edev->get_ethaddr(edev, ethaddr);
if (!ret)
found = 1;
}
if (found)
register_preset_mac_address(edev, ethaddr);
if (IS_ENABLED(CONFIG_OFDEVICE) && edev->parent &&
edev->parent->device_node)
edev->nodepath = xstrdup(edev->parent->device_node->full_name);
if (!eth_current)
eth_current = edev;
return 0;
}
void eth_unregister(struct eth_device *edev)
{
if (edev == eth_current)
eth_current = NULL;
if (IS_ENABLED(CONFIG_OFDEVICE))
free(edev->nodepath);
unregister_device(&edev->dev);
list_del(&edev->list);
}
void led_trigger_network(enum led_trigger trigger)
{
led_trigger(trigger, TRIGGER_FLASH);
led_trigger(LED_TRIGGER_NET_TXRX, TRIGGER_FLASH);
}