// SPDX-License-Identifier: GPL-2.0-or-later /* * * arch/xtensa/platforms/iss/network.c * * Platform specific initialization. * * Authors: Chris Zankel * Based on work form the UML team. * * Copyright 2005 Tensilica Inc. */ #define pr_fmt(fmt) "%s: " fmt, __func__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DRIVER_NAME "iss-netdev" #define ETH_MAX_PACKET 1500 #define ETH_HEADER_OTHER 14 #define ISS_NET_TIMER_VALUE (HZ / 10) /* ------------------------------------------------------------------------- */ /* We currently only support the TUNTAP transport protocol. */ #define TRANSPORT_TUNTAP_NAME "tuntap" #define TRANSPORT_TUNTAP_MTU ETH_MAX_PACKET struct tuntap_info { char dev_name[IFNAMSIZ]; int fd; }; /* ------------------------------------------------------------------------- */ struct iss_net_private; struct iss_net_ops { int (*open)(struct iss_net_private *lp); void (*close)(struct iss_net_private *lp); int (*read)(struct iss_net_private *lp, struct sk_buff **skb); int (*write)(struct iss_net_private *lp, struct sk_buff **skb); unsigned short (*protocol)(struct sk_buff *skb); int (*poll)(struct iss_net_private *lp); }; /* This structure contains out private information for the driver. */ struct iss_net_private { spinlock_t lock; struct net_device *dev; struct platform_device pdev; struct timer_list tl; struct rtnl_link_stats64 stats; struct timer_list timer; unsigned int timer_val; int index; int mtu; struct { union { struct tuntap_info tuntap; } info; const struct iss_net_ops *net_ops; } tp; }; /* ================================ HELPERS ================================ */ static char *split_if_spec(char *str, ...) { char **arg, *end; va_list ap; va_start(ap, str); while ((arg = va_arg(ap, char**)) != NULL) { if (*str == '\0') { va_end(ap); return NULL; } end = strchr(str, ','); if (end != str) *arg = str; if (end == NULL) { va_end(ap); return NULL; } *end++ = '\0'; str = end; } va_end(ap); return str; } /* Set Ethernet address of the specified device. */ static void setup_etheraddr(struct net_device *dev, char *str) { u8 addr[ETH_ALEN]; if (str == NULL) goto random; if (!mac_pton(str, addr)) { pr_err("%s: failed to parse '%s' as an ethernet address\n", dev->name, str); goto random; } if (is_multicast_ether_addr(addr)) { pr_err("%s: attempt to assign a multicast ethernet address\n", dev->name); goto random; } if (!is_valid_ether_addr(addr)) { pr_err("%s: attempt to assign an invalid ethernet address\n", dev->name); goto random; } if (!is_local_ether_addr(addr)) pr_warn("%s: assigning a globally valid ethernet address\n", dev->name); eth_hw_addr_set(dev, addr); return; random: pr_info("%s: choosing a random ethernet address\n", dev->name); eth_hw_addr_random(dev); } /* ======================= TUNTAP TRANSPORT INTERFACE ====================== */ static int tuntap_open(struct iss_net_private *lp) { struct ifreq ifr; char *dev_name = lp->tp.info.tuntap.dev_name; int err = -EINVAL; int fd; fd = simc_open("/dev/net/tun", 02, 0); /* O_RDWR */ if (fd < 0) { pr_err("%s: failed to open /dev/net/tun, returned %d (errno = %d)\n", lp->dev->name, fd, errno); return fd; } memset(&ifr, 0, sizeof(ifr)); ifr.ifr_flags = IFF_TAP | IFF_NO_PI; strscpy(ifr.ifr_name, dev_name, sizeof(ifr.ifr_name)); err = simc_ioctl(fd, TUNSETIFF, &ifr); if (err < 0) { pr_err("%s: failed to set interface %s, returned %d (errno = %d)\n", lp->dev->name, dev_name, err, errno); simc_close(fd); return err; } lp->tp.info.tuntap.fd = fd; return err; } static void tuntap_close(struct iss_net_private *lp) { simc_close(lp->tp.info.tuntap.fd); lp->tp.info.tuntap.fd = -1; } static int tuntap_read(struct iss_net_private *lp, struct sk_buff **skb) { return simc_read(lp->tp.info.tuntap.fd, (*skb)->data, (*skb)->dev->mtu + ETH_HEADER_OTHER); } static int tuntap_write(struct iss_net_private *lp, struct sk_buff **skb) { return simc_write(lp->tp.info.tuntap.fd, (*skb)->data, (*skb)->len); } unsigned short tuntap_protocol(struct sk_buff *skb) { return eth_type_trans(skb, skb->dev); } static int tuntap_poll(struct iss_net_private *lp) { return simc_poll(lp->tp.info.tuntap.fd); } static const struct iss_net_ops tuntap_ops = { .open = tuntap_open, .close = tuntap_close, .read = tuntap_read, .write = tuntap_write, .protocol = tuntap_protocol, .poll = tuntap_poll, }; /* * ethX=tuntap,[mac address],device name */ static int tuntap_probe(struct iss_net_private *lp, int index, char *init) { struct net_device *dev = lp->dev; char *dev_name = NULL, *mac_str = NULL, *rem = NULL; /* Transport should be 'tuntap': ethX=tuntap,mac,dev_name */ if (strncmp(init, TRANSPORT_TUNTAP_NAME, sizeof(TRANSPORT_TUNTAP_NAME) - 1)) return 0; init += sizeof(TRANSPORT_TUNTAP_NAME) - 1; if (*init == ',') { rem = split_if_spec(init + 1, &mac_str, &dev_name, NULL); if (rem != NULL) { pr_err("%s: extra garbage on specification : '%s'\n", dev->name, rem); return 0; } } else if (*init != '\0') { pr_err("%s: invalid argument: %s. Skipping device!\n", dev->name, init); return 0; } if (!dev_name) { pr_err("%s: missing tuntap device name\n", dev->name); return 0; } strscpy(lp->tp.info.tuntap.dev_name, dev_name, sizeof(lp->tp.info.tuntap.dev_name)); setup_etheraddr(dev, mac_str); lp->mtu = TRANSPORT_TUNTAP_MTU; lp->tp.info.tuntap.fd = -1; lp->tp.net_ops = &tuntap_ops; return 1; } /* ================================ ISS NET ================================ */ static int iss_net_rx(struct net_device *dev) { struct iss_net_private *lp = netdev_priv(dev); int pkt_len; struct sk_buff *skb; /* Check if there is any new data. */ if (lp->tp.net_ops->poll(lp) == 0) return 0; /* Try to allocate memory, if it fails, try again next round. */ skb = dev_alloc_skb(dev->mtu + 2 + ETH_HEADER_OTHER); if (skb == NULL) { spin_lock_bh(&lp->lock); lp->stats.rx_dropped++; spin_unlock_bh(&lp->lock); return 0; } skb_reserve(skb, 2); /* Setup skb */ skb->dev = dev; skb_reset_mac_header(skb); pkt_len = lp->tp.net_ops->read(lp, &skb); skb_put(skb, pkt_len); if (pkt_len > 0) { skb_trim(skb, pkt_len); skb->protocol = lp->tp.net_ops->protocol(skb); spin_lock_bh(&lp->lock); lp->stats.rx_bytes += skb->len; lp->stats.rx_packets++; spin_unlock_bh(&lp->lock); netif_rx(skb); return pkt_len; } kfree_skb(skb); return pkt_len; } static int iss_net_poll(struct iss_net_private *lp) { int err, ret = 0; if (!netif_running(lp->dev)) return 0; while ((err = iss_net_rx(lp->dev)) > 0) ret++; if (err < 0) { pr_err("Device '%s' read returned %d, shutting it down\n", lp->dev->name, err); dev_close(lp->dev); } else { /* FIXME reactivate_fd(lp->fd, ISS_ETH_IRQ); */ } return ret; } static void iss_net_timer(struct timer_list *t) { struct iss_net_private *lp = from_timer(lp, t, timer); iss_net_poll(lp); mod_timer(&lp->timer, jiffies + lp->timer_val); } static int iss_net_open(struct net_device *dev) { struct iss_net_private *lp = netdev_priv(dev); int err; err = lp->tp.net_ops->open(lp); if (err < 0) return err; netif_start_queue(dev); /* clear buffer - it can happen that the host side of the interface * is full when we get here. In this case, new data is never queued, * SIGIOs never arrive, and the net never works. */ while ((err = iss_net_rx(dev)) > 0) ; timer_setup(&lp->timer, iss_net_timer, 0); lp->timer_val = ISS_NET_TIMER_VALUE; mod_timer(&lp->timer, jiffies + lp->timer_val); return err; } static int iss_net_close(struct net_device *dev) { struct iss_net_private *lp = netdev_priv(dev); netif_stop_queue(dev); del_timer_sync(&lp->timer); lp->tp.net_ops->close(lp); return 0; } static int iss_net_start_xmit(struct sk_buff *skb, struct net_device *dev) { struct iss_net_private *lp = netdev_priv(dev); int len; netif_stop_queue(dev); len = lp->tp.net_ops->write(lp, &skb); if (len == skb->len) { spin_lock_bh(&lp->lock); lp->stats.tx_packets++; lp->stats.tx_bytes += skb->len; spin_unlock_bh(&lp->lock); netif_trans_update(dev); netif_start_queue(dev); /* this is normally done in the interrupt when tx finishes */ netif_wake_queue(dev); } else if (len == 0) { netif_start_queue(dev); spin_lock_bh(&lp->lock); lp->stats.tx_dropped++; spin_unlock_bh(&lp->lock); } else { netif_start_queue(dev); pr_err("%s: %s failed(%d)\n", dev->name, __func__, len); } dev_kfree_skb(skb); return NETDEV_TX_OK; } static void iss_net_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats) { struct iss_net_private *lp = netdev_priv(dev); spin_lock_bh(&lp->lock); *stats = lp->stats; spin_unlock_bh(&lp->lock); } static void iss_net_set_multicast_list(struct net_device *dev) { } static void iss_net_tx_timeout(struct net_device *dev, unsigned int txqueue) { } static int iss_net_change_mtu(struct net_device *dev, int new_mtu) { return -EINVAL; } void iss_net_user_timer_expire(struct timer_list *unused) { } static struct platform_driver iss_net_driver = { .driver = { .name = DRIVER_NAME, }, }; static int driver_registered; static const struct net_device_ops iss_netdev_ops = { .ndo_open = iss_net_open, .ndo_stop = iss_net_close, .ndo_get_stats64 = iss_net_get_stats64, .ndo_start_xmit = iss_net_start_xmit, .ndo_validate_addr = eth_validate_addr, .ndo_change_mtu = iss_net_change_mtu, .ndo_set_mac_address = eth_mac_addr, .ndo_tx_timeout = iss_net_tx_timeout, .ndo_set_rx_mode = iss_net_set_multicast_list, }; static void iss_net_pdev_release(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); struct iss_net_private *lp = container_of(pdev, struct iss_net_private, pdev); free_netdev(lp->dev); } static void iss_net_configure(int index, char *init) { struct net_device *dev; struct iss_net_private *lp; dev = alloc_etherdev(sizeof(*lp)); if (dev == NULL) { pr_err("eth_configure: failed to allocate device\n"); return; } /* Initialize private element. */ lp = netdev_priv(dev); *lp = (struct iss_net_private) { .dev = dev, .index = index, }; spin_lock_init(&lp->lock); /* * If this name ends up conflicting with an existing registered * netdevice, that is OK, register_netdev{,ice}() will notice this * and fail. */ snprintf(dev->name, sizeof(dev->name), "eth%d", index); /* * Try all transport protocols. * Note: more protocols can be added by adding '&& !X_init(lp, eth)'. */ if (!tuntap_probe(lp, index, init)) { pr_err("%s: invalid arguments. Skipping device!\n", dev->name); goto err_free_netdev; } pr_info("Netdevice %d (%pM)\n", index, dev->dev_addr); /* sysfs register */ if (!driver_registered) { if (platform_driver_register(&iss_net_driver)) goto err_free_netdev; driver_registered = 1; } lp->pdev.id = index; lp->pdev.name = DRIVER_NAME; lp->pdev.dev.release = iss_net_pdev_release; if (platform_device_register(&lp->pdev)) goto err_free_netdev; SET_NETDEV_DEV(dev, &lp->pdev.dev); dev->netdev_ops = &iss_netdev_ops; dev->mtu = lp->mtu; dev->watchdog_timeo = (HZ >> 1); dev->irq = -1; rtnl_lock(); if (register_netdevice(dev)) { rtnl_unlock(); pr_err("%s: error registering net device!\n", dev->name); platform_device_unregister(&lp->pdev); return; } rtnl_unlock(); timer_setup(&lp->tl, iss_net_user_timer_expire, 0); return; err_free_netdev: free_netdev(dev); } /* ------------------------------------------------------------------------- */ /* Filled in during early boot */ struct list_head eth_cmd_line = LIST_HEAD_INIT(eth_cmd_line); struct iss_net_init { struct list_head list; char *init; /* init string */ int index; }; /* * Parse the command line and look for 'ethX=...' fields, and register all * those fields. They will be later initialized in iss_net_init. */ static int __init iss_net_setup(char *str) { struct iss_net_init *device = NULL; struct iss_net_init *new; struct list_head *ele; char *end; int rc; unsigned n; end = strchr(str, '='); if (!end) { pr_err("Expected '=' after device number\n"); return 1; } *end = 0; rc = kstrtouint(str, 0, &n); *end = '='; if (rc < 0) { pr_err("Failed to parse '%s'\n", str); return 1; } str = end; list_for_each(ele, ð_cmd_line) { device = list_entry(ele, struct iss_net_init, list); if (device->index == n) break; } if (device && device->index == n) { pr_err("Device %u already configured\n", n); return 1; } new = memblock_alloc(sizeof(*new), SMP_CACHE_BYTES); if (new == NULL) { pr_err("Alloc_bootmem failed\n"); return 1; } INIT_LIST_HEAD(&new->list); new->index = n; new->init = str + 1; list_add_tail(&new->list, ð_cmd_line); return 1; } __setup("eth", iss_net_setup); /* * Initialize all ISS Ethernet devices previously registered in iss_net_setup. */ static int iss_net_init(void) { struct list_head *ele, *next; /* Walk through all Ethernet devices specified in the command line. */ list_for_each_safe(ele, next, ð_cmd_line) { struct iss_net_init *eth; eth = list_entry(ele, struct iss_net_init, list); iss_net_configure(eth->index, eth->init); } return 1; } device_initcall(iss_net_init);