7aeac9ef18
Run on LLNW canaries and tested by pho@ gallatin: Using a 14-core, 28-HTT single socket E5-2697 v3 with a 40GbE MLX5 based ConnectX 4-LX NIC, I see an almost 12% improvement in received packet rate, and a larger improvement in bytes delivered all the way to userspace. When the host receiving 64 streams of netperf -H $DUT -t UDP_STREAM -- -m 1, I see, using nstat -I mce0 1 before the patch: InMpps OMpps InGbs OGbs err TCP Est %CPU syscalls csw irq GBfree 4.98 0.00 4.42 0.00 4235592 33 83.80 4720653 2149771 1235 247.32 4.73 0.00 4.20 0.00 4025260 33 82.99 4724900 2139833 1204 247.32 4.72 0.00 4.20 0.00 4035252 33 82.14 4719162 2132023 1264 247.32 4.71 0.00 4.21 0.00 4073206 33 83.68 4744973 2123317 1347 247.32 4.72 0.00 4.21 0.00 4061118 33 80.82 4713615 2188091 1490 247.32 4.72 0.00 4.21 0.00 4051675 33 85.29 4727399 2109011 1205 247.32 4.73 0.00 4.21 0.00 4039056 33 84.65 4724735 2102603 1053 247.32 After the patch InMpps OMpps InGbs OGbs err TCP Est %CPU syscalls csw irq GBfree 5.43 0.00 4.20 0.00 3313143 33 84.96 5434214 1900162 2656 245.51 5.43 0.00 4.20 0.00 3308527 33 85.24 5439695 1809382 2521 245.51 5.42 0.00 4.19 0.00 3316778 33 87.54 5416028 1805835 2256 245.51 5.42 0.00 4.19 0.00 3317673 33 90.44 5426044 1763056 2332 245.51 5.42 0.00 4.19 0.00 3314839 33 88.11 5435732 1792218 2499 245.52 5.44 0.00 4.19 0.00 3293228 33 91.84 5426301 1668597 2121 245.52 Similarly, netperf reports 230Mb/s before the patch, and 270Mb/s after the patch Reviewed by: gallatin Sponsored by: Limelight Networks Differential Revision: https://reviews.freebsd.org/D15366
1956 lines
45 KiB
C
1956 lines
45 KiB
C
/*-
|
|
* Copyright (c) 2009 Yohanes Nugroho <yohanes@gmail.com>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/mbuf.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/module.h>
|
|
#include <sys/rman.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/sockio.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/taskqueue.h>
|
|
|
|
#include <net/ethernet.h>
|
|
#include <net/if.h>
|
|
#include <net/if_arp.h>
|
|
#include <net/if_dl.h>
|
|
#include <net/if_media.h>
|
|
#include <net/if_types.h>
|
|
#include <net/if_var.h>
|
|
#include <net/if_vlan_var.h>
|
|
|
|
#ifdef INET
|
|
#include <netinet/in.h>
|
|
#include <netinet/in_systm.h>
|
|
#include <netinet/in_var.h>
|
|
#include <netinet/ip.h>
|
|
#endif
|
|
|
|
#include <net/bpf.h>
|
|
#include <net/bpfdesc.h>
|
|
|
|
#include <dev/mii/mii.h>
|
|
#include <dev/mii/miivar.h>
|
|
|
|
#include <arm/cavium/cns11xx/if_ecereg.h>
|
|
#include <arm/cavium/cns11xx/if_ecevar.h>
|
|
#include <arm/cavium/cns11xx/econa_var.h>
|
|
|
|
#include <machine/bus.h>
|
|
#include <machine/intr.h>
|
|
|
|
/* "device miibus" required. See GENERIC if you get errors here. */
|
|
#include "miibus_if.h"
|
|
|
|
static uint8_t
|
|
vlan0_mac[ETHER_ADDR_LEN] = {0x00, 0xaa, 0xbb, 0xcc, 0xdd, 0x19};
|
|
|
|
/*
|
|
* Boot loader expects the hardware state to be the same when we
|
|
* restart the device (warm boot), so we need to save the initial
|
|
* config values.
|
|
*/
|
|
int initial_switch_config;
|
|
int initial_cpu_config;
|
|
int initial_port0_config;
|
|
int initial_port1_config;
|
|
|
|
static inline uint32_t
|
|
read_4(struct ece_softc *sc, bus_size_t off)
|
|
{
|
|
|
|
return (bus_read_4(sc->mem_res, off));
|
|
}
|
|
|
|
static inline void
|
|
write_4(struct ece_softc *sc, bus_size_t off, uint32_t val)
|
|
{
|
|
|
|
bus_write_4(sc->mem_res, off, val);
|
|
}
|
|
|
|
#define ECE_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
|
|
#define ECE_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
|
|
#define ECE_LOCK_INIT(_sc) \
|
|
mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->dev), \
|
|
MTX_NETWORK_LOCK, MTX_DEF)
|
|
|
|
#define ECE_TXLOCK(_sc) mtx_lock(&(_sc)->sc_mtx_tx)
|
|
#define ECE_TXUNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx_tx)
|
|
#define ECE_TXLOCK_INIT(_sc) \
|
|
mtx_init(&_sc->sc_mtx_tx, device_get_nameunit(_sc->dev), \
|
|
"ECE TX Lock", MTX_DEF)
|
|
|
|
#define ECE_CLEANUPLOCK(_sc) mtx_lock(&(_sc)->sc_mtx_cleanup)
|
|
#define ECE_CLEANUPUNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx_cleanup)
|
|
#define ECE_CLEANUPLOCK_INIT(_sc) \
|
|
mtx_init(&_sc->sc_mtx_cleanup, device_get_nameunit(_sc->dev), \
|
|
"ECE cleanup Lock", MTX_DEF)
|
|
|
|
#define ECE_RXLOCK(_sc) mtx_lock(&(_sc)->sc_mtx_rx)
|
|
#define ECE_RXUNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx_rx)
|
|
#define ECE_RXLOCK_INIT(_sc) \
|
|
mtx_init(&_sc->sc_mtx_rx, device_get_nameunit(_sc->dev), \
|
|
"ECE RX Lock", MTX_DEF)
|
|
|
|
#define ECE_LOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx);
|
|
#define ECE_TXLOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx_tx);
|
|
#define ECE_RXLOCK_DESTROY(_sc) mtx_destroy(&_sc->sc_mtx_rx);
|
|
#define ECE_CLEANUPLOCK_DESTROY(_sc) \
|
|
mtx_destroy(&_sc->sc_mtx_cleanup);
|
|
|
|
#define ECE_ASSERT_LOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_OWNED);
|
|
#define ECE_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->sc_mtx, MA_NOTOWNED);
|
|
|
|
static devclass_t ece_devclass;
|
|
|
|
/* ifnet entry points */
|
|
|
|
static void eceinit_locked(void *);
|
|
static void ecestart_locked(struct ifnet *);
|
|
|
|
static void eceinit(void *);
|
|
static void ecestart(struct ifnet *);
|
|
static void ecestop(struct ece_softc *);
|
|
static int eceioctl(struct ifnet * ifp, u_long, caddr_t);
|
|
|
|
/* bus entry points */
|
|
|
|
static int ece_probe(device_t dev);
|
|
static int ece_attach(device_t dev);
|
|
static int ece_detach(device_t dev);
|
|
static void ece_intr(void *);
|
|
static void ece_intr_qf(void *);
|
|
static void ece_intr_status(void *xsc);
|
|
|
|
/* helper routines */
|
|
static int ece_activate(device_t dev);
|
|
static void ece_deactivate(device_t dev);
|
|
static int ece_ifmedia_upd(struct ifnet *ifp);
|
|
static void ece_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr);
|
|
static int ece_get_mac(struct ece_softc *sc, u_char *eaddr);
|
|
static void ece_set_mac(struct ece_softc *sc, u_char *eaddr);
|
|
static int configure_cpu_port(struct ece_softc *sc);
|
|
static int configure_lan_port(struct ece_softc *sc, int phy_type);
|
|
static void set_pvid(struct ece_softc *sc, int port0, int port1, int cpu);
|
|
static void set_vlan_vid(struct ece_softc *sc, int vlan);
|
|
static void set_vlan_member(struct ece_softc *sc, int vlan);
|
|
static void set_vlan_tag(struct ece_softc *sc, int vlan);
|
|
static int hardware_init(struct ece_softc *sc);
|
|
static void ece_intr_rx_locked(struct ece_softc *sc, int count);
|
|
|
|
static void ece_free_desc_dma_tx(struct ece_softc *sc);
|
|
static void ece_free_desc_dma_rx(struct ece_softc *sc);
|
|
|
|
static void ece_intr_task(void *arg, int pending __unused);
|
|
static void ece_tx_task(void *arg, int pending __unused);
|
|
static void ece_cleanup_task(void *arg, int pending __unused);
|
|
|
|
static int ece_allocate_dma(struct ece_softc *sc);
|
|
|
|
static void ece_intr_tx(void *xsc);
|
|
|
|
static void clear_mac_entries(struct ece_softc *ec, int include_this_mac);
|
|
|
|
static uint32_t read_mac_entry(struct ece_softc *ec,
|
|
uint8_t *mac_result,
|
|
int first);
|
|
|
|
/*PHY related functions*/
|
|
static inline int
|
|
phy_read(struct ece_softc *sc, int phy, int reg)
|
|
{
|
|
int val;
|
|
int ii;
|
|
int status;
|
|
|
|
write_4(sc, PHY_CONTROL, PHY_RW_OK);
|
|
write_4(sc, PHY_CONTROL,
|
|
(PHY_ADDRESS(phy)|PHY_READ_COMMAND |
|
|
PHY_REGISTER(reg)));
|
|
|
|
for (ii = 0; ii < 0x1000; ii++) {
|
|
status = read_4(sc, PHY_CONTROL);
|
|
if (status & PHY_RW_OK) {
|
|
/* Clear the rw_ok status, and clear other
|
|
* bits value. */
|
|
write_4(sc, PHY_CONTROL, PHY_RW_OK);
|
|
val = PHY_GET_DATA(status);
|
|
return (val);
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
static inline void
|
|
phy_write(struct ece_softc *sc, int phy, int reg, int data)
|
|
{
|
|
int ii;
|
|
|
|
write_4(sc, PHY_CONTROL, PHY_RW_OK);
|
|
write_4(sc, PHY_CONTROL,
|
|
PHY_ADDRESS(phy) | PHY_REGISTER(reg) |
|
|
PHY_WRITE_COMMAND | PHY_DATA(data));
|
|
for (ii = 0; ii < 0x1000; ii++) {
|
|
if (read_4(sc, PHY_CONTROL) & PHY_RW_OK) {
|
|
/* Clear the rw_ok status, and clear other
|
|
* bits value.
|
|
*/
|
|
write_4(sc, PHY_CONTROL, PHY_RW_OK);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int get_phy_type(struct ece_softc *sc)
|
|
{
|
|
uint16_t phy0_id = 0, phy1_id = 0;
|
|
|
|
/*
|
|
* Use SMI (MDC/MDIO) to read Link Partner's PHY Identifier
|
|
* Register 1.
|
|
*/
|
|
phy0_id = phy_read(sc, 0, 0x2);
|
|
phy1_id = phy_read(sc, 1, 0x2);
|
|
|
|
if ((phy0_id == 0xFFFF) && (phy1_id == 0x000F))
|
|
return (ASIX_GIGA_PHY);
|
|
else if ((phy0_id == 0x0243) && (phy1_id == 0x0243))
|
|
return (TWO_SINGLE_PHY);
|
|
else if ((phy0_id == 0xFFFF) && (phy1_id == 0x0007))
|
|
return (VSC8601_GIGA_PHY);
|
|
else if ((phy0_id == 0x0243) && (phy1_id == 0xFFFF))
|
|
return (IC_PLUS_PHY);
|
|
|
|
return (NOT_FOUND_PHY);
|
|
}
|
|
|
|
static int
|
|
ece_probe(device_t dev)
|
|
{
|
|
|
|
device_set_desc(dev, "Econa Ethernet Controller");
|
|
return (0);
|
|
}
|
|
|
|
|
|
static int
|
|
ece_attach(device_t dev)
|
|
{
|
|
struct ece_softc *sc;
|
|
struct ifnet *ifp = NULL;
|
|
struct sysctl_ctx_list *sctx;
|
|
struct sysctl_oid *soid;
|
|
u_char eaddr[ETHER_ADDR_LEN];
|
|
int err;
|
|
int i, rid;
|
|
uint32_t rnd;
|
|
|
|
err = 0;
|
|
|
|
sc = device_get_softc(dev);
|
|
|
|
sc->dev = dev;
|
|
|
|
rid = 0;
|
|
sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
|
|
RF_ACTIVE);
|
|
if (sc->mem_res == NULL)
|
|
goto out;
|
|
|
|
power_on_network_interface();
|
|
|
|
rid = 0;
|
|
sc->irq_res_status = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
|
|
RF_ACTIVE);
|
|
if (sc->irq_res_status == NULL)
|
|
goto out;
|
|
|
|
rid = 1;
|
|
/*TSTC: Fm-Switch-Tx-Complete*/
|
|
sc->irq_res_tx = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
|
|
RF_ACTIVE);
|
|
if (sc->irq_res_tx == NULL)
|
|
goto out;
|
|
|
|
rid = 2;
|
|
/*FSRC: Fm-Switch-Rx-Complete*/
|
|
sc->irq_res_rec = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
|
|
RF_ACTIVE);
|
|
if (sc->irq_res_rec == NULL)
|
|
goto out;
|
|
|
|
rid = 4;
|
|
/*FSQF: Fm-Switch-Queue-Full*/
|
|
sc->irq_res_qf = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
|
|
RF_ACTIVE);
|
|
if (sc->irq_res_qf == NULL)
|
|
goto out;
|
|
|
|
err = ece_activate(dev);
|
|
if (err)
|
|
goto out;
|
|
|
|
/* Sysctls */
|
|
sctx = device_get_sysctl_ctx(dev);
|
|
soid = device_get_sysctl_tree(dev);
|
|
|
|
ECE_LOCK_INIT(sc);
|
|
|
|
callout_init_mtx(&sc->tick_ch, &sc->sc_mtx, 0);
|
|
|
|
if ((err = ece_get_mac(sc, eaddr)) != 0) {
|
|
/* No MAC address configured. Generate the random one. */
|
|
if (bootverbose)
|
|
device_printf(dev,
|
|
"Generating random ethernet address.\n");
|
|
rnd = arc4random();
|
|
|
|
/*from if_ae.c/if_ate.c*/
|
|
/*
|
|
* Set OUI to convenient locally assigned address. 'b'
|
|
* is 0x62, which has the locally assigned bit set, and
|
|
* the broadcast/multicast bit clear.
|
|
*/
|
|
eaddr[0] = 'b';
|
|
eaddr[1] = 's';
|
|
eaddr[2] = 'd';
|
|
eaddr[3] = (rnd >> 16) & 0xff;
|
|
eaddr[4] = (rnd >> 8) & 0xff;
|
|
eaddr[5] = rnd & 0xff;
|
|
|
|
for (i = 0; i < ETHER_ADDR_LEN; i++)
|
|
eaddr[i] = vlan0_mac[i];
|
|
}
|
|
ece_set_mac(sc, eaddr);
|
|
sc->ifp = ifp = if_alloc(IFT_ETHER);
|
|
/* Only one PHY at address 0 in this device. */
|
|
err = mii_attach(dev, &sc->miibus, ifp, ece_ifmedia_upd,
|
|
ece_ifmedia_sts, BMSR_DEFCAPMASK, 0, MII_OFFSET_ANY, 0);
|
|
if (err != 0) {
|
|
device_printf(dev, "attaching PHYs failed\n");
|
|
goto out;
|
|
}
|
|
ifp->if_softc = sc;
|
|
if_initname(ifp, device_get_name(dev), device_get_unit(dev));
|
|
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
|
|
|
|
ifp->if_capabilities = IFCAP_HWCSUM;
|
|
|
|
ifp->if_hwassist = (CSUM_IP | CSUM_TCP | CSUM_UDP);
|
|
ifp->if_capenable = ifp->if_capabilities;
|
|
ifp->if_start = ecestart;
|
|
ifp->if_ioctl = eceioctl;
|
|
ifp->if_init = eceinit;
|
|
ifp->if_snd.ifq_drv_maxlen = ECE_MAX_TX_BUFFERS - 1;
|
|
IFQ_SET_MAXLEN(&ifp->if_snd, ECE_MAX_TX_BUFFERS - 1);
|
|
IFQ_SET_READY(&ifp->if_snd);
|
|
|
|
/* Create local taskq. */
|
|
|
|
TASK_INIT(&sc->sc_intr_task, 0, ece_intr_task, sc);
|
|
TASK_INIT(&sc->sc_tx_task, 1, ece_tx_task, ifp);
|
|
TASK_INIT(&sc->sc_cleanup_task, 2, ece_cleanup_task, sc);
|
|
sc->sc_tq = taskqueue_create_fast("ece_taskq", M_WAITOK,
|
|
taskqueue_thread_enqueue,
|
|
&sc->sc_tq);
|
|
if (sc->sc_tq == NULL) {
|
|
device_printf(sc->dev, "could not create taskqueue\n");
|
|
goto out;
|
|
}
|
|
|
|
ether_ifattach(ifp, eaddr);
|
|
|
|
/*
|
|
* Activate interrupts
|
|
*/
|
|
err = bus_setup_intr(dev, sc->irq_res_rec, INTR_TYPE_NET | INTR_MPSAFE,
|
|
NULL, ece_intr, sc, &sc->intrhand);
|
|
if (err) {
|
|
ether_ifdetach(ifp);
|
|
ECE_LOCK_DESTROY(sc);
|
|
goto out;
|
|
}
|
|
|
|
err = bus_setup_intr(dev, sc->irq_res_status,
|
|
INTR_TYPE_NET | INTR_MPSAFE,
|
|
NULL, ece_intr_status, sc, &sc->intrhand_status);
|
|
if (err) {
|
|
ether_ifdetach(ifp);
|
|
ECE_LOCK_DESTROY(sc);
|
|
goto out;
|
|
}
|
|
|
|
err = bus_setup_intr(dev, sc->irq_res_qf, INTR_TYPE_NET | INTR_MPSAFE,
|
|
NULL,ece_intr_qf, sc, &sc->intrhand_qf);
|
|
|
|
if (err) {
|
|
ether_ifdetach(ifp);
|
|
ECE_LOCK_DESTROY(sc);
|
|
goto out;
|
|
}
|
|
|
|
err = bus_setup_intr(dev, sc->irq_res_tx, INTR_TYPE_NET | INTR_MPSAFE,
|
|
NULL, ece_intr_tx, sc, &sc->intrhand_tx);
|
|
|
|
if (err) {
|
|
ether_ifdetach(ifp);
|
|
ECE_LOCK_DESTROY(sc);
|
|
goto out;
|
|
}
|
|
|
|
ECE_TXLOCK_INIT(sc);
|
|
ECE_RXLOCK_INIT(sc);
|
|
ECE_CLEANUPLOCK_INIT(sc);
|
|
|
|
/* Enable all interrupt sources. */
|
|
write_4(sc, INTERRUPT_MASK, 0x00000000);
|
|
|
|
/* Enable port 0. */
|
|
write_4(sc, PORT_0_CONFIG, read_4(sc, PORT_0_CONFIG) & ~(PORT_DISABLE));
|
|
|
|
taskqueue_start_threads(&sc->sc_tq, 1, PI_NET, "%s taskq",
|
|
device_get_nameunit(sc->dev));
|
|
|
|
out:
|
|
if (err)
|
|
ece_deactivate(dev);
|
|
if (err && ifp)
|
|
if_free(ifp);
|
|
return (err);
|
|
}
|
|
|
|
static int
|
|
ece_detach(device_t dev)
|
|
{
|
|
struct ece_softc *sc = device_get_softc(dev);
|
|
struct ifnet *ifp = sc->ifp;
|
|
|
|
ecestop(sc);
|
|
if (ifp != NULL) {
|
|
ether_ifdetach(ifp);
|
|
if_free(ifp);
|
|
}
|
|
ece_deactivate(dev);
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
ece_getaddr(void *arg, bus_dma_segment_t *segs, int nsegs, int error)
|
|
{
|
|
u_int32_t *paddr;
|
|
KASSERT(nsegs == 1, ("wrong number of segments, should be 1"));
|
|
paddr = arg;
|
|
*paddr = segs->ds_addr;
|
|
}
|
|
|
|
static int
|
|
ece_alloc_desc_dma_tx(struct ece_softc *sc)
|
|
{
|
|
int i;
|
|
int error;
|
|
|
|
/* Allocate a busdma tag and DMA safe memory for TX/RX descriptors. */
|
|
error = bus_dma_tag_create(sc->sc_parent_tag, /* parent */
|
|
16, 0, /* alignment, boundary */
|
|
BUS_SPACE_MAXADDR_32BIT, /* lowaddr */
|
|
BUS_SPACE_MAXADDR, /* highaddr */
|
|
NULL, NULL, /* filtfunc, filtfuncarg */
|
|
sizeof(eth_tx_desc_t)*ECE_MAX_TX_BUFFERS, /* max size */
|
|
1, /*nsegments */
|
|
sizeof(eth_tx_desc_t)*ECE_MAX_TX_BUFFERS,
|
|
0, /* flags */
|
|
NULL, NULL, /* lockfunc, lockfuncarg */
|
|
&sc->dmatag_data_tx); /* dmat */
|
|
|
|
/* Allocate memory for TX ring. */
|
|
error = bus_dmamem_alloc(sc->dmatag_data_tx,
|
|
(void**)&(sc->desc_tx),
|
|
BUS_DMA_NOWAIT | BUS_DMA_ZERO |
|
|
BUS_DMA_COHERENT,
|
|
&(sc->dmamap_ring_tx));
|
|
|
|
if (error) {
|
|
if_printf(sc->ifp, "failed to allocate DMA memory\n");
|
|
bus_dma_tag_destroy(sc->dmatag_data_tx);
|
|
sc->dmatag_data_tx = 0;
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Load Ring DMA. */
|
|
error = bus_dmamap_load(sc->dmatag_data_tx, sc->dmamap_ring_tx,
|
|
sc->desc_tx,
|
|
sizeof(eth_tx_desc_t)*ECE_MAX_TX_BUFFERS,
|
|
ece_getaddr,
|
|
&(sc->ring_paddr_tx), BUS_DMA_NOWAIT);
|
|
|
|
if (error) {
|
|
if_printf(sc->ifp, "can't load descriptor\n");
|
|
bus_dmamem_free(sc->dmatag_data_tx, sc->desc_tx,
|
|
sc->dmamap_ring_tx);
|
|
sc->desc_tx = NULL;
|
|
bus_dma_tag_destroy(sc->dmatag_data_tx);
|
|
sc->dmatag_data_tx = 0;
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Allocate a busdma tag for mbufs. Alignment is 2 bytes */
|
|
error = bus_dma_tag_create(sc->sc_parent_tag, /* parent */
|
|
1, 0, /* alignment, boundary */
|
|
BUS_SPACE_MAXADDR_32BIT, /* lowaddr */
|
|
BUS_SPACE_MAXADDR, /* highaddr */
|
|
NULL, NULL, /* filtfunc, filtfuncarg */
|
|
MCLBYTES*MAX_FRAGMENT, /* maxsize */
|
|
MAX_FRAGMENT, /* nsegments */
|
|
MCLBYTES, 0, /* maxsegsz, flags */
|
|
NULL, NULL, /* lockfunc, lockfuncarg */
|
|
&sc->dmatag_ring_tx); /* dmat */
|
|
|
|
if (error) {
|
|
if_printf(sc->ifp, "failed to create busdma tag for mbufs\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
for (i = 0; i < ECE_MAX_TX_BUFFERS; i++) {
|
|
/* Create dma map for each descriptor. */
|
|
error = bus_dmamap_create(sc->dmatag_ring_tx, 0,
|
|
&(sc->tx_desc[i].dmamap));
|
|
if (error) {
|
|
if_printf(sc->ifp, "failed to create map for mbuf\n");
|
|
return (ENXIO);
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
ece_free_desc_dma_tx(struct ece_softc *sc)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ECE_MAX_TX_BUFFERS; i++) {
|
|
if (sc->tx_desc[i].buff) {
|
|
m_freem(sc->tx_desc[i].buff);
|
|
sc->tx_desc[i].buff= 0;
|
|
}
|
|
}
|
|
|
|
if (sc->ring_paddr_tx) {
|
|
bus_dmamap_unload(sc->dmatag_data_tx, sc->dmamap_ring_tx);
|
|
sc->ring_paddr_tx = 0;
|
|
}
|
|
|
|
if (sc->desc_tx) {
|
|
bus_dmamem_free(sc->dmatag_data_tx,
|
|
sc->desc_tx, sc->dmamap_ring_tx);
|
|
sc->desc_tx = NULL;
|
|
}
|
|
|
|
if (sc->dmatag_data_tx) {
|
|
bus_dma_tag_destroy(sc->dmatag_data_tx);
|
|
sc->dmatag_data_tx = 0;
|
|
}
|
|
|
|
if (sc->dmatag_ring_tx) {
|
|
for (i = 0; i<ECE_MAX_TX_BUFFERS; i++) {
|
|
bus_dmamap_destroy(sc->dmatag_ring_tx,
|
|
sc->tx_desc[i].dmamap);
|
|
sc->tx_desc[i].dmamap = 0;
|
|
}
|
|
bus_dma_tag_destroy(sc->dmatag_ring_tx);
|
|
sc->dmatag_ring_tx = 0;
|
|
}
|
|
}
|
|
|
|
static int
|
|
ece_alloc_desc_dma_rx(struct ece_softc *sc)
|
|
{
|
|
int error;
|
|
int i;
|
|
|
|
/* Allocate a busdma tag and DMA safe memory for RX descriptors. */
|
|
error = bus_dma_tag_create(sc->sc_parent_tag, /* parent */
|
|
16, 0, /* alignment, boundary */
|
|
BUS_SPACE_MAXADDR_32BIT, /* lowaddr */
|
|
BUS_SPACE_MAXADDR, /* highaddr */
|
|
NULL, NULL, /* filtfunc, filtfuncarg */
|
|
/* maxsize, nsegments */
|
|
sizeof(eth_rx_desc_t)*ECE_MAX_RX_BUFFERS, 1,
|
|
/* maxsegsz, flags */
|
|
sizeof(eth_rx_desc_t)*ECE_MAX_RX_BUFFERS, 0,
|
|
NULL, NULL, /* lockfunc, lockfuncarg */
|
|
&sc->dmatag_data_rx); /* dmat */
|
|
|
|
/* Allocate RX ring. */
|
|
error = bus_dmamem_alloc(sc->dmatag_data_rx,
|
|
(void**)&(sc->desc_rx),
|
|
BUS_DMA_NOWAIT | BUS_DMA_ZERO |
|
|
BUS_DMA_COHERENT,
|
|
&(sc->dmamap_ring_rx));
|
|
|
|
if (error) {
|
|
if_printf(sc->ifp, "failed to allocate DMA memory\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Load dmamap. */
|
|
error = bus_dmamap_load(sc->dmatag_data_rx, sc->dmamap_ring_rx,
|
|
sc->desc_rx,
|
|
sizeof(eth_rx_desc_t)*ECE_MAX_RX_BUFFERS,
|
|
ece_getaddr,
|
|
&(sc->ring_paddr_rx), BUS_DMA_NOWAIT);
|
|
|
|
if (error) {
|
|
if_printf(sc->ifp, "can't load descriptor\n");
|
|
bus_dmamem_free(sc->dmatag_data_rx, sc->desc_rx,
|
|
sc->dmamap_ring_rx);
|
|
bus_dma_tag_destroy(sc->dmatag_data_rx);
|
|
sc->desc_rx = NULL;
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Allocate a busdma tag for mbufs. */
|
|
error = bus_dma_tag_create(sc->sc_parent_tag,/* parent */
|
|
16, 0, /* alignment, boundary */
|
|
BUS_SPACE_MAXADDR_32BIT, /* lowaddr */
|
|
BUS_SPACE_MAXADDR, /* highaddr */
|
|
NULL, NULL, /* filtfunc, filtfuncarg */
|
|
MCLBYTES, 1, /* maxsize, nsegments */
|
|
MCLBYTES, 0, /* maxsegsz, flags */
|
|
NULL, NULL, /* lockfunc, lockfuncarg */
|
|
&sc->dmatag_ring_rx); /* dmat */
|
|
|
|
if (error) {
|
|
if_printf(sc->ifp, "failed to create busdma tag for mbufs\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
for (i = 0; i<ECE_MAX_RX_BUFFERS; i++) {
|
|
error = bus_dmamap_create(sc->dmatag_ring_rx, 0,
|
|
&sc->rx_desc[i].dmamap);
|
|
if (error) {
|
|
if_printf(sc->ifp, "failed to create map for mbuf\n");
|
|
return (ENXIO);
|
|
}
|
|
}
|
|
|
|
error = bus_dmamap_create(sc->dmatag_ring_rx, 0, &sc->rx_sparemap);
|
|
if (error) {
|
|
if_printf(sc->ifp, "failed to create spare map\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
ece_free_desc_dma_rx(struct ece_softc *sc)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ECE_MAX_RX_BUFFERS; i++) {
|
|
if (sc->rx_desc[i].buff) {
|
|
m_freem(sc->rx_desc[i].buff);
|
|
sc->rx_desc[i].buff = NULL;
|
|
}
|
|
}
|
|
|
|
if (sc->ring_paddr_rx) {
|
|
bus_dmamap_unload(sc->dmatag_data_rx, sc->dmamap_ring_rx);
|
|
sc->ring_paddr_rx = 0;
|
|
}
|
|
|
|
if (sc->desc_rx) {
|
|
bus_dmamem_free(sc->dmatag_data_rx, sc->desc_rx,
|
|
sc->dmamap_ring_rx);
|
|
sc->desc_rx = NULL;
|
|
}
|
|
|
|
if (sc->dmatag_data_rx) {
|
|
bus_dma_tag_destroy(sc->dmatag_data_rx);
|
|
sc->dmatag_data_rx = NULL;
|
|
}
|
|
|
|
if (sc->dmatag_ring_rx) {
|
|
for (i = 0; i < ECE_MAX_RX_BUFFERS; i++)
|
|
bus_dmamap_destroy(sc->dmatag_ring_rx,
|
|
sc->rx_desc[i].dmamap);
|
|
bus_dmamap_destroy(sc->dmatag_ring_rx, sc->rx_sparemap);
|
|
bus_dma_tag_destroy(sc->dmatag_ring_rx);
|
|
sc->dmatag_ring_rx = NULL;
|
|
}
|
|
}
|
|
|
|
static int
|
|
ece_new_rxbuf(struct ece_softc *sc, struct rx_desc_info* descinfo)
|
|
{
|
|
struct mbuf *new_mbuf;
|
|
bus_dma_segment_t seg[1];
|
|
bus_dmamap_t map;
|
|
int error;
|
|
int nsegs;
|
|
bus_dma_tag_t tag;
|
|
|
|
tag = sc->dmatag_ring_rx;
|
|
|
|
new_mbuf = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
|
|
|
|
if (new_mbuf == NULL)
|
|
return (ENOBUFS);
|
|
|
|
new_mbuf->m_len = new_mbuf->m_pkthdr.len = MCLBYTES;
|
|
|
|
error = bus_dmamap_load_mbuf_sg(tag, sc->rx_sparemap, new_mbuf,
|
|
seg, &nsegs, BUS_DMA_NOWAIT);
|
|
|
|
KASSERT(nsegs == 1, ("Too many segments returned!"));
|
|
|
|
if (nsegs != 1 || error) {
|
|
m_free(new_mbuf);
|
|
return (ENOBUFS);
|
|
}
|
|
|
|
if (descinfo->buff != NULL) {
|
|
bus_dmamap_sync(tag, descinfo->dmamap, BUS_DMASYNC_POSTREAD);
|
|
bus_dmamap_unload(tag, descinfo->dmamap);
|
|
}
|
|
|
|
map = descinfo->dmamap;
|
|
descinfo->dmamap = sc->rx_sparemap;
|
|
sc->rx_sparemap = map;
|
|
|
|
bus_dmamap_sync(tag, descinfo->dmamap, BUS_DMASYNC_PREREAD);
|
|
|
|
descinfo->buff = new_mbuf;
|
|
descinfo->desc->data_ptr = seg->ds_addr;
|
|
descinfo->desc->length = seg->ds_len - 2;
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
ece_allocate_dma(struct ece_softc *sc)
|
|
{
|
|
eth_tx_desc_t *desctx;
|
|
eth_rx_desc_t *descrx;
|
|
int i;
|
|
int error;
|
|
|
|
/* Create parent tag for tx and rx */
|
|
error = bus_dma_tag_create(
|
|
bus_get_dma_tag(sc->dev),/* parent */
|
|
1, 0, /* alignment, boundary */
|
|
BUS_SPACE_MAXADDR, /* lowaddr */
|
|
BUS_SPACE_MAXADDR, /* highaddr */
|
|
NULL, NULL, /* filter, filterarg */
|
|
BUS_SPACE_MAXSIZE_32BIT, 0,/* maxsize, nsegments */
|
|
BUS_SPACE_MAXSIZE_32BIT, /* maxsegsize */
|
|
0, /* flags */
|
|
NULL, NULL, /* lockfunc, lockarg */
|
|
&sc->sc_parent_tag);
|
|
|
|
ece_alloc_desc_dma_tx(sc);
|
|
|
|
for (i = 0; i < ECE_MAX_TX_BUFFERS; i++) {
|
|
desctx = (eth_tx_desc_t *)(&sc->desc_tx[i]);
|
|
memset(desctx, 0, sizeof(eth_tx_desc_t));
|
|
desctx->length = MAX_PACKET_LEN;
|
|
desctx->cown = 1;
|
|
if (i == ECE_MAX_TX_BUFFERS - 1)
|
|
desctx->eor = 1;
|
|
}
|
|
|
|
ece_alloc_desc_dma_rx(sc);
|
|
|
|
for (i = 0; i < ECE_MAX_RX_BUFFERS; i++) {
|
|
descrx = &(sc->desc_rx[i]);
|
|
memset(descrx, 0, sizeof(eth_rx_desc_t));
|
|
sc->rx_desc[i].desc = descrx;
|
|
sc->rx_desc[i].buff = 0;
|
|
ece_new_rxbuf(sc, &(sc->rx_desc[i]));
|
|
|
|
if (i == ECE_MAX_RX_BUFFERS - 1)
|
|
descrx->eor = 1;
|
|
}
|
|
sc->tx_prod = 0;
|
|
sc->tx_cons = 0;
|
|
sc->last_rx = 0;
|
|
sc->desc_curr_tx = 0;
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
ece_activate(device_t dev)
|
|
{
|
|
struct ece_softc *sc;
|
|
int err;
|
|
uint32_t mac_port_config;
|
|
struct ifnet *ifp;
|
|
|
|
sc = device_get_softc(dev);
|
|
ifp = sc->ifp;
|
|
|
|
initial_switch_config = read_4(sc, SWITCH_CONFIG);
|
|
initial_cpu_config = read_4(sc, CPU_PORT_CONFIG);
|
|
initial_port0_config = read_4(sc, MAC_PORT_0_CONFIG);
|
|
initial_port1_config = read_4(sc, MAC_PORT_1_CONFIG);
|
|
|
|
/* Disable Port 0 */
|
|
mac_port_config = read_4(sc, MAC_PORT_0_CONFIG);
|
|
mac_port_config |= (PORT_DISABLE);
|
|
write_4(sc, MAC_PORT_0_CONFIG, mac_port_config);
|
|
|
|
/* Disable Port 1 */
|
|
mac_port_config = read_4(sc, MAC_PORT_1_CONFIG);
|
|
mac_port_config |= (PORT_DISABLE);
|
|
write_4(sc, MAC_PORT_1_CONFIG, mac_port_config);
|
|
|
|
err = ece_allocate_dma(sc);
|
|
if (err) {
|
|
if_printf(sc->ifp, "failed allocating dma\n");
|
|
goto out;
|
|
}
|
|
|
|
write_4(sc, TS_DESCRIPTOR_POINTER, sc->ring_paddr_tx);
|
|
write_4(sc, TS_DESCRIPTOR_BASE_ADDR, sc->ring_paddr_tx);
|
|
|
|
write_4(sc, FS_DESCRIPTOR_POINTER, sc->ring_paddr_rx);
|
|
write_4(sc, FS_DESCRIPTOR_BASE_ADDR, sc->ring_paddr_rx);
|
|
|
|
write_4(sc, FS_DMA_CONTROL, 1);
|
|
|
|
return (0);
|
|
out:
|
|
return (ENXIO);
|
|
|
|
}
|
|
|
|
static void
|
|
ece_deactivate(device_t dev)
|
|
{
|
|
struct ece_softc *sc;
|
|
|
|
sc = device_get_softc(dev);
|
|
|
|
if (sc->intrhand)
|
|
bus_teardown_intr(dev, sc->irq_res_rec, sc->intrhand);
|
|
|
|
sc->intrhand = 0;
|
|
|
|
if (sc->intrhand_qf)
|
|
bus_teardown_intr(dev, sc->irq_res_qf, sc->intrhand_qf);
|
|
|
|
sc->intrhand_qf = 0;
|
|
|
|
bus_generic_detach(sc->dev);
|
|
if (sc->miibus)
|
|
device_delete_child(sc->dev, sc->miibus);
|
|
if (sc->mem_res)
|
|
bus_release_resource(dev, SYS_RES_IOPORT,
|
|
rman_get_rid(sc->mem_res), sc->mem_res);
|
|
sc->mem_res = 0;
|
|
|
|
if (sc->irq_res_rec)
|
|
bus_release_resource(dev, SYS_RES_IRQ,
|
|
rman_get_rid(sc->irq_res_rec), sc->irq_res_rec);
|
|
|
|
if (sc->irq_res_qf)
|
|
bus_release_resource(dev, SYS_RES_IRQ,
|
|
rman_get_rid(sc->irq_res_qf), sc->irq_res_qf);
|
|
|
|
if (sc->irq_res_qf)
|
|
bus_release_resource(dev, SYS_RES_IRQ,
|
|
rman_get_rid(sc->irq_res_status), sc->irq_res_status);
|
|
|
|
sc->irq_res_rec = 0;
|
|
sc->irq_res_qf = 0;
|
|
sc->irq_res_status = 0;
|
|
ECE_TXLOCK_DESTROY(sc);
|
|
ECE_RXLOCK_DESTROY(sc);
|
|
|
|
ece_free_desc_dma_tx(sc);
|
|
ece_free_desc_dma_rx(sc);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Change media according to request.
|
|
*/
|
|
static int
|
|
ece_ifmedia_upd(struct ifnet *ifp)
|
|
{
|
|
struct ece_softc *sc = ifp->if_softc;
|
|
struct mii_data *mii;
|
|
int error;
|
|
|
|
mii = device_get_softc(sc->miibus);
|
|
ECE_LOCK(sc);
|
|
error = mii_mediachg(mii);
|
|
ECE_UNLOCK(sc);
|
|
return (error);
|
|
}
|
|
|
|
/*
|
|
* Notify the world which media we're using.
|
|
*/
|
|
static void
|
|
ece_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr)
|
|
{
|
|
struct ece_softc *sc = ifp->if_softc;
|
|
struct mii_data *mii;
|
|
|
|
mii = device_get_softc(sc->miibus);
|
|
ECE_LOCK(sc);
|
|
mii_pollstat(mii);
|
|
ifmr->ifm_active = mii->mii_media_active;
|
|
ifmr->ifm_status = mii->mii_media_status;
|
|
ECE_UNLOCK(sc);
|
|
}
|
|
|
|
static void
|
|
ece_tick(void *xsc)
|
|
{
|
|
struct ece_softc *sc = xsc;
|
|
struct mii_data *mii;
|
|
int active;
|
|
|
|
mii = device_get_softc(sc->miibus);
|
|
active = mii->mii_media_active;
|
|
mii_tick(mii);
|
|
|
|
/*
|
|
* Schedule another timeout one second from now.
|
|
*/
|
|
callout_reset(&sc->tick_ch, hz, ece_tick, sc);
|
|
}
|
|
|
|
static uint32_t
|
|
read_mac_entry(struct ece_softc *ec,
|
|
uint8_t *mac_result,
|
|
int first)
|
|
{
|
|
uint32_t ii;
|
|
struct arl_table_entry_t entry;
|
|
uint32_t *entry_val;
|
|
write_4(ec, ARL_TABLE_ACCESS_CONTROL_0, 0);
|
|
write_4(ec, ARL_TABLE_ACCESS_CONTROL_1, 0);
|
|
write_4(ec, ARL_TABLE_ACCESS_CONTROL_2, 0);
|
|
if (first)
|
|
write_4(ec, ARL_TABLE_ACCESS_CONTROL_0, 0x1);
|
|
else
|
|
write_4(ec, ARL_TABLE_ACCESS_CONTROL_0, 0x2);
|
|
|
|
for (ii = 0; ii < 0x1000; ii++)
|
|
if (read_4(ec, ARL_TABLE_ACCESS_CONTROL_1) & (0x1))
|
|
break;
|
|
|
|
entry_val = (uint32_t*) (&entry);
|
|
entry_val[0] = read_4(ec, ARL_TABLE_ACCESS_CONTROL_1);
|
|
entry_val[1] = read_4(ec, ARL_TABLE_ACCESS_CONTROL_2);
|
|
|
|
if (mac_result)
|
|
memcpy(mac_result, entry.mac_addr, ETHER_ADDR_LEN);
|
|
|
|
return (entry.table_end);
|
|
}
|
|
|
|
static uint32_t
|
|
write_arl_table_entry(struct ece_softc *ec,
|
|
uint32_t filter,
|
|
uint32_t vlan_mac,
|
|
uint32_t vlan_gid,
|
|
uint32_t age_field,
|
|
uint32_t port_map,
|
|
const uint8_t *mac_addr)
|
|
{
|
|
uint32_t ii;
|
|
uint32_t *entry_val;
|
|
struct arl_table_entry_t entry;
|
|
|
|
memset(&entry, 0, sizeof(entry));
|
|
|
|
entry.filter = filter;
|
|
entry.vlan_mac = vlan_mac;
|
|
entry.vlan_gid = vlan_gid;
|
|
entry.age_field = age_field;
|
|
entry.port_map = port_map;
|
|
memcpy(entry.mac_addr, mac_addr, ETHER_ADDR_LEN);
|
|
|
|
entry_val = (uint32_t*) (&entry);
|
|
|
|
write_4(ec, ARL_TABLE_ACCESS_CONTROL_0, 0);
|
|
write_4(ec, ARL_TABLE_ACCESS_CONTROL_1, 0);
|
|
write_4(ec, ARL_TABLE_ACCESS_CONTROL_2, 0);
|
|
|
|
write_4(ec, ARL_TABLE_ACCESS_CONTROL_1, entry_val[0]);
|
|
write_4(ec, ARL_TABLE_ACCESS_CONTROL_2, entry_val[1]);
|
|
|
|
write_4(ec, ARL_TABLE_ACCESS_CONTROL_0, ARL_WRITE_COMMAND);
|
|
|
|
for (ii = 0; ii < 0x1000; ii++)
|
|
if (read_4(ec, ARL_TABLE_ACCESS_CONTROL_1) &
|
|
ARL_COMMAND_COMPLETE)
|
|
return (1); /* Write OK. */
|
|
|
|
/* Write failed. */
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
remove_mac_entry(struct ece_softc *sc,
|
|
uint8_t *mac)
|
|
{
|
|
|
|
/* Invalid age_field mean erase this entry. */
|
|
write_arl_table_entry(sc, 0, 1, VLAN0_GROUP_ID,
|
|
INVALID_ENTRY, VLAN0_GROUP,
|
|
mac);
|
|
}
|
|
|
|
static void
|
|
add_mac_entry(struct ece_softc *sc,
|
|
uint8_t *mac)
|
|
{
|
|
|
|
write_arl_table_entry(sc, 0, 1, VLAN0_GROUP_ID,
|
|
NEW_ENTRY, VLAN0_GROUP,
|
|
mac);
|
|
}
|
|
|
|
/**
|
|
* The behavior of ARL table reading and deletion is not well defined
|
|
* in the documentation. To be safe, all mac addresses are put to a
|
|
* list, then deleted.
|
|
*
|
|
*/
|
|
static void
|
|
clear_mac_entries(struct ece_softc *ec, int include_this_mac)
|
|
{
|
|
int table_end;
|
|
struct mac_list * temp;
|
|
struct mac_list * mac_list_header;
|
|
struct mac_list * current;
|
|
char mac[ETHER_ADDR_LEN];
|
|
|
|
current = NULL;
|
|
mac_list_header = NULL;
|
|
|
|
table_end = read_mac_entry(ec, mac, 1);
|
|
while (!table_end) {
|
|
if (!include_this_mac &&
|
|
memcmp(mac, vlan0_mac, ETHER_ADDR_LEN) == 0) {
|
|
/* Read next entry. */
|
|
table_end = read_mac_entry(ec, mac, 0);
|
|
continue;
|
|
}
|
|
|
|
temp = (struct mac_list*)malloc(sizeof(struct mac_list),
|
|
M_DEVBUF,
|
|
M_NOWAIT | M_ZERO);
|
|
memcpy(temp->mac_addr, mac, ETHER_ADDR_LEN);
|
|
temp->next = 0;
|
|
if (mac_list_header) {
|
|
current->next = temp;
|
|
current = temp;
|
|
} else {
|
|
mac_list_header = temp;
|
|
current = temp;
|
|
}
|
|
/* Read next Entry */
|
|
table_end = read_mac_entry(ec, mac, 0);
|
|
}
|
|
|
|
current = mac_list_header;
|
|
|
|
while (current) {
|
|
remove_mac_entry(ec, current->mac_addr);
|
|
temp = current;
|
|
current = current->next;
|
|
free(temp, M_DEVBUF);
|
|
}
|
|
}
|
|
|
|
static int
|
|
configure_lan_port(struct ece_softc *sc, int phy_type)
|
|
{
|
|
uint32_t sw_config;
|
|
uint32_t mac_port_config;
|
|
|
|
/*
|
|
* Configure switch
|
|
*/
|
|
sw_config = read_4(sc, SWITCH_CONFIG);
|
|
/* Enable fast aging. */
|
|
sw_config |= FAST_AGING;
|
|
/* Enable IVL learning. */
|
|
sw_config |= IVL_LEARNING;
|
|
/* Disable hardware NAT. */
|
|
sw_config &= ~(HARDWARE_NAT);
|
|
|
|
sw_config |= SKIP_L2_LOOKUP_PORT_0 | SKIP_L2_LOOKUP_PORT_1| NIC_MODE;
|
|
|
|
write_4(sc, SWITCH_CONFIG, sw_config);
|
|
|
|
sw_config = read_4(sc, SWITCH_CONFIG);
|
|
|
|
mac_port_config = read_4(sc, MAC_PORT_0_CONFIG);
|
|
|
|
if (!(mac_port_config & 0x1) || (mac_port_config & 0x2))
|
|
if_printf(sc->ifp, "Link Down\n");
|
|
else
|
|
write_4(sc, MAC_PORT_0_CONFIG, mac_port_config);
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
set_pvid(struct ece_softc *sc, int port0, int port1, int cpu)
|
|
{
|
|
uint32_t val;
|
|
val = read_4(sc, VLAN_PORT_PVID) & (~(0x7 << 0));
|
|
write_4(sc, VLAN_PORT_PVID, val);
|
|
val = read_4(sc, VLAN_PORT_PVID) | ((port0) & 0x07);
|
|
write_4(sc, VLAN_PORT_PVID, val);
|
|
val = read_4(sc, VLAN_PORT_PVID) & (~(0x7 << 4));
|
|
write_4(sc, VLAN_PORT_PVID, val);
|
|
val = read_4(sc, VLAN_PORT_PVID) | (((port1) & 0x07) << 4);
|
|
write_4(sc, VLAN_PORT_PVID, val);
|
|
|
|
val = read_4(sc, VLAN_PORT_PVID) & (~(0x7 << 8));
|
|
write_4(sc, VLAN_PORT_PVID, val);
|
|
val = read_4(sc, VLAN_PORT_PVID) | (((cpu) & 0x07) << 8);
|
|
write_4(sc, VLAN_PORT_PVID, val);
|
|
|
|
}
|
|
|
|
/* VLAN related functions */
|
|
static void
|
|
set_vlan_vid(struct ece_softc *sc, int vlan)
|
|
{
|
|
const uint32_t regs[] = {
|
|
VLAN_VID_0_1,
|
|
VLAN_VID_0_1,
|
|
VLAN_VID_2_3,
|
|
VLAN_VID_2_3,
|
|
VLAN_VID_4_5,
|
|
VLAN_VID_4_5,
|
|
VLAN_VID_6_7,
|
|
VLAN_VID_6_7
|
|
};
|
|
|
|
const int vids[] = {
|
|
VLAN0_VID,
|
|
VLAN1_VID,
|
|
VLAN2_VID,
|
|
VLAN3_VID,
|
|
VLAN4_VID,
|
|
VLAN5_VID,
|
|
VLAN6_VID,
|
|
VLAN7_VID
|
|
};
|
|
|
|
uint32_t val;
|
|
uint32_t reg;
|
|
int vid;
|
|
|
|
reg = regs[vlan];
|
|
vid = vids[vlan];
|
|
|
|
if (vlan & 1) {
|
|
val = read_4(sc, reg);
|
|
write_4(sc, reg, val & (~(0xFFF << 0)));
|
|
val = read_4(sc, reg);
|
|
write_4(sc, reg, val|((vid & 0xFFF) << 0));
|
|
} else {
|
|
val = read_4(sc, reg);
|
|
write_4(sc, reg, val & (~(0xFFF << 12)));
|
|
val = read_4(sc, reg);
|
|
write_4(sc, reg, val|((vid & 0xFFF) << 12));
|
|
}
|
|
}
|
|
|
|
static void
|
|
set_vlan_member(struct ece_softc *sc, int vlan)
|
|
{
|
|
unsigned char shift;
|
|
uint32_t val;
|
|
int group;
|
|
const int groups[] = {
|
|
VLAN0_GROUP,
|
|
VLAN1_GROUP,
|
|
VLAN2_GROUP,
|
|
VLAN3_GROUP,
|
|
VLAN4_GROUP,
|
|
VLAN5_GROUP,
|
|
VLAN6_GROUP,
|
|
VLAN7_GROUP
|
|
};
|
|
|
|
group = groups[vlan];
|
|
|
|
shift = vlan*3;
|
|
val = read_4(sc, VLAN_MEMBER_PORT_MAP) & (~(0x7 << shift));
|
|
write_4(sc, VLAN_MEMBER_PORT_MAP, val);
|
|
val = read_4(sc, VLAN_MEMBER_PORT_MAP);
|
|
write_4(sc, VLAN_MEMBER_PORT_MAP, val | ((group & 0x7) << shift));
|
|
}
|
|
|
|
static void
|
|
set_vlan_tag(struct ece_softc *sc, int vlan)
|
|
{
|
|
unsigned char shift;
|
|
uint32_t val;
|
|
|
|
int tag = 0;
|
|
|
|
shift = vlan*3;
|
|
val = read_4(sc, VLAN_TAG_PORT_MAP) & (~(0x7 << shift));
|
|
write_4(sc, VLAN_TAG_PORT_MAP, val);
|
|
val = read_4(sc, VLAN_TAG_PORT_MAP);
|
|
write_4(sc, VLAN_TAG_PORT_MAP, val | ((tag & 0x7) << shift));
|
|
}
|
|
|
|
static int
|
|
configure_cpu_port(struct ece_softc *sc)
|
|
{
|
|
uint32_t cpu_port_config;
|
|
int i;
|
|
|
|
cpu_port_config = read_4(sc, CPU_PORT_CONFIG);
|
|
/* SA learning Disable */
|
|
cpu_port_config |= (SA_LEARNING_DISABLE);
|
|
/* set data offset + 2 */
|
|
cpu_port_config &= ~(1U << 31);
|
|
|
|
write_4(sc, CPU_PORT_CONFIG, cpu_port_config);
|
|
|
|
if (!write_arl_table_entry(sc, 0, 1, VLAN0_GROUP_ID,
|
|
STATIC_ENTRY, VLAN0_GROUP,
|
|
vlan0_mac))
|
|
return (1);
|
|
|
|
set_pvid(sc, PORT0_PVID, PORT1_PVID, CPU_PORT_PVID);
|
|
|
|
for (i = 0; i < 8; i++) {
|
|
set_vlan_vid(sc, i);
|
|
set_vlan_member(sc, i);
|
|
set_vlan_tag(sc, i);
|
|
}
|
|
|
|
/* disable all interrupt status sources */
|
|
write_4(sc, INTERRUPT_MASK, 0xffff1fff);
|
|
|
|
/* clear previous interrupt sources */
|
|
write_4(sc, INTERRUPT_STATUS, 0x00001FFF);
|
|
|
|
write_4(sc, TS_DMA_CONTROL, 0);
|
|
write_4(sc, FS_DMA_CONTROL, 0);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
hardware_init(struct ece_softc *sc)
|
|
{
|
|
int status = 0;
|
|
static int gw_phy_type;
|
|
|
|
gw_phy_type = get_phy_type(sc);
|
|
/* Currently only ic_plus phy is supported. */
|
|
if (gw_phy_type != IC_PLUS_PHY) {
|
|
device_printf(sc->dev, "PHY type is not supported (%d)\n",
|
|
gw_phy_type);
|
|
return (-1);
|
|
}
|
|
status = configure_lan_port(sc, gw_phy_type);
|
|
configure_cpu_port(sc);
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
set_mac_address(struct ece_softc *sc, const char *mac, int mac_len)
|
|
{
|
|
|
|
/* Invalid age_field mean erase this entry. */
|
|
write_arl_table_entry(sc, 0, 1, VLAN0_GROUP_ID,
|
|
INVALID_ENTRY, VLAN0_GROUP,
|
|
mac);
|
|
memcpy(vlan0_mac, mac, ETHER_ADDR_LEN);
|
|
|
|
write_arl_table_entry(sc, 0, 1, VLAN0_GROUP_ID,
|
|
STATIC_ENTRY, VLAN0_GROUP,
|
|
mac);
|
|
}
|
|
|
|
static void
|
|
ece_set_mac(struct ece_softc *sc, u_char *eaddr)
|
|
{
|
|
memcpy(vlan0_mac, eaddr, ETHER_ADDR_LEN);
|
|
set_mac_address(sc, eaddr, ETHER_ADDR_LEN);
|
|
}
|
|
|
|
/*
|
|
* TODO: the device doesn't have MAC stored, we should read the
|
|
* configuration stored in FLASH, but the format depends on the
|
|
* bootloader used.*
|
|
*/
|
|
static int
|
|
ece_get_mac(struct ece_softc *sc, u_char *eaddr)
|
|
{
|
|
return (ENXIO);
|
|
}
|
|
|
|
static void
|
|
ece_intr_rx_locked(struct ece_softc *sc, int count)
|
|
{
|
|
struct ifnet *ifp = sc->ifp;
|
|
struct mbuf *mb;
|
|
struct rx_desc_info *rxdesc;
|
|
eth_rx_desc_t *desc;
|
|
|
|
int fssd_curr;
|
|
int fssd;
|
|
int i;
|
|
int idx;
|
|
int rxcount;
|
|
uint32_t status;
|
|
|
|
fssd_curr = read_4(sc, FS_DESCRIPTOR_POINTER);
|
|
|
|
fssd = (fssd_curr - (uint32_t)sc->ring_paddr_rx)>>4;
|
|
|
|
desc = sc->rx_desc[sc->last_rx].desc;
|
|
|
|
/* Prepare to read the data in the ring. */
|
|
bus_dmamap_sync(sc->dmatag_ring_rx,
|
|
sc->dmamap_ring_rx,
|
|
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
|
|
|
|
if (fssd > sc->last_rx)
|
|
rxcount = fssd - sc->last_rx;
|
|
else if (fssd < sc->last_rx)
|
|
rxcount = (ECE_MAX_RX_BUFFERS - sc->last_rx) + fssd;
|
|
else {
|
|
if (desc->cown == 0)
|
|
return;
|
|
else
|
|
rxcount = ECE_MAX_RX_BUFFERS;
|
|
}
|
|
|
|
for (i= 0; i < rxcount; i++) {
|
|
status = desc->cown;
|
|
if (!status)
|
|
break;
|
|
|
|
idx = sc->last_rx;
|
|
rxdesc = &sc->rx_desc[idx];
|
|
mb = rxdesc->buff;
|
|
|
|
if (desc->length < ETHER_MIN_LEN - ETHER_CRC_LEN ||
|
|
desc->length > ETHER_MAX_LEN - ETHER_CRC_LEN +
|
|
ETHER_VLAN_ENCAP_LEN) {
|
|
if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
|
|
desc->cown = 0;
|
|
desc->length = MCLBYTES - 2;
|
|
/* Invalid packet, skip and process next
|
|
* packet.
|
|
*/
|
|
continue;
|
|
}
|
|
|
|
if (ece_new_rxbuf(sc, rxdesc) != 0) {
|
|
if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1);
|
|
desc->cown = 0;
|
|
desc->length = MCLBYTES - 2;
|
|
break;
|
|
}
|
|
|
|
/**
|
|
* The device will write to addrress + 2 So we need to adjust
|
|
* the address after the packet is received.
|
|
*/
|
|
mb->m_data += 2;
|
|
mb->m_len = mb->m_pkthdr.len = desc->length;
|
|
|
|
mb->m_flags |= M_PKTHDR;
|
|
mb->m_pkthdr.rcvif = ifp;
|
|
if ((ifp->if_capenable & IFCAP_RXCSUM) != 0) {
|
|
/*check for valid checksum*/
|
|
if ( (!desc->l4f) && (desc->prot != 3)) {
|
|
mb->m_pkthdr.csum_flags |= CSUM_IP_CHECKED;
|
|
mb->m_pkthdr.csum_flags |= CSUM_IP_VALID;
|
|
mb->m_pkthdr.csum_data = 0xffff;
|
|
}
|
|
}
|
|
ECE_RXUNLOCK(sc);
|
|
(*ifp->if_input)(ifp, mb);
|
|
ECE_RXLOCK(sc);
|
|
|
|
desc->cown = 0;
|
|
desc->length = MCLBYTES - 2;
|
|
|
|
bus_dmamap_sync(sc->dmatag_ring_rx,
|
|
sc->dmamap_ring_rx,
|
|
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
|
|
|
|
if (sc->last_rx == ECE_MAX_RX_BUFFERS - 1)
|
|
sc->last_rx = 0;
|
|
else
|
|
sc->last_rx++;
|
|
|
|
desc = sc->rx_desc[sc->last_rx].desc;
|
|
}
|
|
|
|
/* Sync updated flags. */
|
|
bus_dmamap_sync(sc->dmatag_ring_rx,
|
|
sc->dmamap_ring_rx,
|
|
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
ece_intr_task(void *arg, int pending __unused)
|
|
{
|
|
struct ece_softc *sc = arg;
|
|
ECE_RXLOCK(sc);
|
|
ece_intr_rx_locked(sc, -1);
|
|
ECE_RXUNLOCK(sc);
|
|
}
|
|
|
|
static void
|
|
ece_intr(void *xsc)
|
|
{
|
|
struct ece_softc *sc = xsc;
|
|
struct ifnet *ifp = sc->ifp;
|
|
|
|
if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) {
|
|
write_4(sc, FS_DMA_CONTROL, 0);
|
|
return;
|
|
}
|
|
|
|
taskqueue_enqueue(sc->sc_tq, &sc->sc_intr_task);
|
|
|
|
if (!IFQ_DRV_IS_EMPTY(&ifp->if_snd))
|
|
taskqueue_enqueue(sc->sc_tq, &sc->sc_tx_task);
|
|
}
|
|
|
|
static void
|
|
ece_intr_status(void *xsc)
|
|
{
|
|
struct ece_softc *sc = xsc;
|
|
struct ifnet *ifp = sc->ifp;
|
|
int stat;
|
|
|
|
stat = read_4(sc, INTERRUPT_STATUS);
|
|
|
|
write_4(sc, INTERRUPT_STATUS, stat);
|
|
|
|
if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) {
|
|
if ((stat & ERROR_MASK) != 0)
|
|
if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ece_cleanup_locked(struct ece_softc *sc)
|
|
{
|
|
eth_tx_desc_t *desc;
|
|
|
|
if (sc->tx_cons == sc->tx_prod) return;
|
|
|
|
/* Prepare to read the ring (owner bit). */
|
|
bus_dmamap_sync(sc->dmatag_ring_tx,
|
|
sc->dmamap_ring_tx,
|
|
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
|
|
|
|
while (sc->tx_cons != sc->tx_prod) {
|
|
desc = sc->tx_desc[sc->tx_cons].desc;
|
|
if (desc->cown != 0) {
|
|
struct tx_desc_info *td = &(sc->tx_desc[sc->tx_cons]);
|
|
/* We are finished with this descriptor ... */
|
|
bus_dmamap_sync(sc->dmatag_data_tx, td->dmamap,
|
|
BUS_DMASYNC_POSTWRITE);
|
|
/* ... and unload, so we can reuse. */
|
|
bus_dmamap_unload(sc->dmatag_data_tx, td->dmamap);
|
|
m_freem(td->buff);
|
|
td->buff = 0;
|
|
sc->tx_cons = (sc->tx_cons + 1) % ECE_MAX_TX_BUFFERS;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static void
|
|
ece_cleanup_task(void *arg, int pending __unused)
|
|
{
|
|
struct ece_softc *sc = arg;
|
|
ECE_CLEANUPLOCK(sc);
|
|
ece_cleanup_locked(sc);
|
|
ECE_CLEANUPUNLOCK(sc);
|
|
}
|
|
|
|
static void
|
|
ece_intr_tx(void *xsc)
|
|
{
|
|
struct ece_softc *sc = xsc;
|
|
struct ifnet *ifp = sc->ifp;
|
|
if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) {
|
|
/* This should not happen, stop DMA. */
|
|
write_4(sc, FS_DMA_CONTROL, 0);
|
|
return;
|
|
}
|
|
taskqueue_enqueue(sc->sc_tq, &sc->sc_cleanup_task);
|
|
}
|
|
|
|
static void
|
|
ece_intr_qf(void *xsc)
|
|
{
|
|
struct ece_softc *sc = xsc;
|
|
struct ifnet *ifp = sc->ifp;
|
|
if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) {
|
|
/* This should not happen, stop DMA. */
|
|
write_4(sc, FS_DMA_CONTROL, 0);
|
|
return;
|
|
}
|
|
taskqueue_enqueue(sc->sc_tq, &sc->sc_intr_task);
|
|
write_4(sc, FS_DMA_CONTROL, 1);
|
|
}
|
|
|
|
/*
|
|
* Reset and initialize the chip
|
|
*/
|
|
static void
|
|
eceinit_locked(void *xsc)
|
|
{
|
|
struct ece_softc *sc = xsc;
|
|
struct ifnet *ifp = sc->ifp;
|
|
struct mii_data *mii;
|
|
uint32_t cfg_reg;
|
|
uint32_t cpu_port_config;
|
|
uint32_t mac_port_config;
|
|
|
|
while (1) {
|
|
cfg_reg = read_4(sc, BIST_RESULT_TEST_0);
|
|
if ((cfg_reg & (1<<17)))
|
|
break;
|
|
DELAY(100);
|
|
}
|
|
/* Set to default values. */
|
|
write_4(sc, SWITCH_CONFIG, 0x007AA7A1);
|
|
write_4(sc, MAC_PORT_0_CONFIG, 0x00423D00);
|
|
write_4(sc, MAC_PORT_1_CONFIG, 0x00423D80);
|
|
write_4(sc, CPU_PORT_CONFIG, 0x004C0000);
|
|
|
|
hardware_init(sc);
|
|
|
|
mac_port_config = read_4(sc, MAC_PORT_0_CONFIG);
|
|
|
|
/* Enable Port 0 */
|
|
mac_port_config &= (~(PORT_DISABLE));
|
|
write_4(sc, MAC_PORT_0_CONFIG, mac_port_config);
|
|
|
|
cpu_port_config = read_4(sc, CPU_PORT_CONFIG);
|
|
/* Enable CPU. */
|
|
cpu_port_config &= ~(PORT_DISABLE);
|
|
write_4(sc, CPU_PORT_CONFIG, cpu_port_config);
|
|
|
|
/*
|
|
* Set 'running' flag, and clear output active flag
|
|
* and attempt to start the output
|
|
*/
|
|
ifp->if_drv_flags |= IFF_DRV_RUNNING;
|
|
ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
|
|
|
|
mii = device_get_softc(sc->miibus);
|
|
mii_pollstat(mii);
|
|
/* Enable DMA. */
|
|
write_4(sc, FS_DMA_CONTROL, 1);
|
|
|
|
callout_reset(&sc->tick_ch, hz, ece_tick, sc);
|
|
}
|
|
|
|
static inline int
|
|
ece_encap(struct ece_softc *sc, struct mbuf *m0)
|
|
{
|
|
struct ifnet *ifp;
|
|
bus_dma_segment_t segs[MAX_FRAGMENT];
|
|
bus_dmamap_t mapp;
|
|
eth_tx_desc_t *desc = NULL;
|
|
int csum_flags;
|
|
int desc_no;
|
|
int error;
|
|
int nsegs;
|
|
int seg;
|
|
|
|
ifp = sc->ifp;
|
|
|
|
/* Fetch unused map */
|
|
mapp = sc->tx_desc[sc->tx_prod].dmamap;
|
|
|
|
error = bus_dmamap_load_mbuf_sg(sc->dmatag_ring_tx, mapp,
|
|
m0, segs, &nsegs,
|
|
BUS_DMA_NOWAIT);
|
|
|
|
if (error != 0) {
|
|
bus_dmamap_unload(sc->dmatag_ring_tx, mapp);
|
|
return ((error != 0) ? error : -1);
|
|
}
|
|
|
|
desc = &(sc->desc_tx[sc->desc_curr_tx]);
|
|
sc->tx_desc[sc->tx_prod].desc = desc;
|
|
sc->tx_desc[sc->tx_prod].buff = m0;
|
|
desc_no = sc->desc_curr_tx;
|
|
|
|
for (seg = 0; seg < nsegs; seg++) {
|
|
if (desc->cown == 0 ) {
|
|
if_printf(ifp, "ERROR: descriptor is still used\n");
|
|
return (-1);
|
|
}
|
|
|
|
desc->length = segs[seg].ds_len;
|
|
desc->data_ptr = segs[seg].ds_addr;
|
|
|
|
if (seg == 0) {
|
|
desc->fs = 1;
|
|
} else {
|
|
desc->fs = 0;
|
|
}
|
|
if (seg == nsegs - 1) {
|
|
desc->ls = 1;
|
|
} else {
|
|
desc->ls = 0;
|
|
}
|
|
|
|
csum_flags = m0->m_pkthdr.csum_flags;
|
|
|
|
desc->fr = 1;
|
|
desc->pmap = 1;
|
|
desc->insv = 0;
|
|
desc->ico = 0;
|
|
desc->tco = 0;
|
|
desc->uco = 0;
|
|
desc->interrupt = 1;
|
|
|
|
if (csum_flags & CSUM_IP) {
|
|
desc->ico = 1;
|
|
if (csum_flags & CSUM_TCP)
|
|
desc->tco = 1;
|
|
if (csum_flags & CSUM_UDP)
|
|
desc->uco = 1;
|
|
}
|
|
|
|
desc++;
|
|
sc->desc_curr_tx = (sc->desc_curr_tx + 1) % ECE_MAX_TX_BUFFERS;
|
|
if (sc->desc_curr_tx == 0) {
|
|
desc = (eth_tx_desc_t *)&(sc->desc_tx[0]);
|
|
}
|
|
}
|
|
|
|
desc = sc->tx_desc[sc->tx_prod].desc;
|
|
|
|
sc->tx_prod = (sc->tx_prod + 1) % ECE_MAX_TX_BUFFERS;
|
|
|
|
/*
|
|
* After all descriptors are set, we set the flags to start the
|
|
* sending process.
|
|
*/
|
|
for (seg = 0; seg < nsegs; seg++) {
|
|
desc->cown = 0;
|
|
desc++;
|
|
desc_no = (desc_no + 1) % ECE_MAX_TX_BUFFERS;
|
|
if (desc_no == 0)
|
|
desc = (eth_tx_desc_t *)&(sc->desc_tx[0]);
|
|
}
|
|
|
|
bus_dmamap_sync(sc->dmatag_data_tx, mapp, BUS_DMASYNC_PREWRITE);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* dequeu packets and transmit
|
|
*/
|
|
static void
|
|
ecestart_locked(struct ifnet *ifp)
|
|
{
|
|
struct ece_softc *sc;
|
|
struct mbuf *m0;
|
|
uint32_t queued = 0;
|
|
|
|
sc = ifp->if_softc;
|
|
if ((ifp->if_drv_flags & (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)) !=
|
|
IFF_DRV_RUNNING)
|
|
return;
|
|
|
|
bus_dmamap_sync(sc->dmatag_ring_tx,
|
|
sc->dmamap_ring_tx,
|
|
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
|
|
|
|
for (;;) {
|
|
/* Get packet from the queue */
|
|
IF_DEQUEUE(&ifp->if_snd, m0);
|
|
if (m0 == NULL)
|
|
break;
|
|
if (ece_encap(sc, m0)) {
|
|
IF_PREPEND(&ifp->if_snd, m0);
|
|
ifp->if_drv_flags |= IFF_DRV_OACTIVE;
|
|
break;
|
|
}
|
|
queued++;
|
|
BPF_MTAP(ifp, m0);
|
|
}
|
|
if (queued) {
|
|
bus_dmamap_sync(sc->dmatag_ring_tx, sc->dmamap_ring_tx,
|
|
BUS_DMASYNC_PREREAD|BUS_DMASYNC_PREWRITE);
|
|
write_4(sc, TS_DMA_CONTROL, 1);
|
|
}
|
|
}
|
|
|
|
static void
|
|
eceinit(void *xsc)
|
|
{
|
|
struct ece_softc *sc = xsc;
|
|
ECE_LOCK(sc);
|
|
eceinit_locked(sc);
|
|
ECE_UNLOCK(sc);
|
|
}
|
|
|
|
static void
|
|
ece_tx_task(void *arg, int pending __unused)
|
|
{
|
|
struct ifnet *ifp;
|
|
ifp = (struct ifnet *)arg;
|
|
ecestart(ifp);
|
|
}
|
|
|
|
static void
|
|
ecestart(struct ifnet *ifp)
|
|
{
|
|
struct ece_softc *sc = ifp->if_softc;
|
|
ECE_TXLOCK(sc);
|
|
ecestart_locked(ifp);
|
|
ECE_TXUNLOCK(sc);
|
|
}
|
|
|
|
/*
|
|
* Turn off interrupts, and stop the nic. Can be called with sc->ifp
|
|
* NULL so be careful.
|
|
*/
|
|
static void
|
|
ecestop(struct ece_softc *sc)
|
|
{
|
|
struct ifnet *ifp = sc->ifp;
|
|
uint32_t mac_port_config;
|
|
|
|
write_4(sc, TS_DMA_CONTROL, 0);
|
|
write_4(sc, FS_DMA_CONTROL, 0);
|
|
|
|
if (ifp)
|
|
ifp->if_drv_flags &= ~(IFF_DRV_RUNNING | IFF_DRV_OACTIVE);
|
|
|
|
callout_stop(&sc->tick_ch);
|
|
|
|
/*Disable Port 0 */
|
|
mac_port_config = read_4(sc, MAC_PORT_0_CONFIG);
|
|
mac_port_config |= (PORT_DISABLE);
|
|
write_4(sc, MAC_PORT_0_CONFIG, mac_port_config);
|
|
|
|
/*Disable Port 1 */
|
|
mac_port_config = read_4(sc, MAC_PORT_1_CONFIG);
|
|
mac_port_config |= (PORT_DISABLE);
|
|
write_4(sc, MAC_PORT_1_CONFIG, mac_port_config);
|
|
|
|
/* Disable all interrupt status sources. */
|
|
write_4(sc, INTERRUPT_MASK, 0x00001FFF);
|
|
|
|
/* Clear previous interrupt sources. */
|
|
write_4(sc, INTERRUPT_STATUS, 0x00001FFF);
|
|
|
|
write_4(sc, SWITCH_CONFIG, initial_switch_config);
|
|
write_4(sc, CPU_PORT_CONFIG, initial_cpu_config);
|
|
write_4(sc, MAC_PORT_0_CONFIG, initial_port0_config);
|
|
write_4(sc, MAC_PORT_1_CONFIG, initial_port1_config);
|
|
|
|
clear_mac_entries(sc, 1);
|
|
}
|
|
|
|
static void
|
|
ece_restart(struct ece_softc *sc)
|
|
{
|
|
struct ifnet *ifp = sc->ifp;
|
|
|
|
ifp->if_drv_flags |= IFF_DRV_RUNNING;
|
|
ifp->if_drv_flags &= ~IFF_DRV_OACTIVE;
|
|
/* Enable port 0. */
|
|
write_4(sc, PORT_0_CONFIG,
|
|
read_4(sc, PORT_0_CONFIG) & ~(PORT_DISABLE));
|
|
write_4(sc, INTERRUPT_MASK, 0x00000000);
|
|
write_4(sc, FS_DMA_CONTROL, 1);
|
|
callout_reset(&sc->tick_ch, hz, ece_tick, sc);
|
|
}
|
|
|
|
static void
|
|
set_filter(struct ece_softc *sc)
|
|
{
|
|
struct ifnet *ifp;
|
|
struct ifmultiaddr *ifma;
|
|
uint32_t mac_port_config;
|
|
|
|
ifp = sc->ifp;
|
|
|
|
clear_mac_entries(sc, 0);
|
|
if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) {
|
|
mac_port_config = read_4(sc, MAC_PORT_0_CONFIG);
|
|
mac_port_config &= ~(DISABLE_BROADCAST_PACKET);
|
|
mac_port_config &= ~(DISABLE_MULTICAST_PACKET);
|
|
write_4(sc, MAC_PORT_0_CONFIG, mac_port_config);
|
|
return;
|
|
}
|
|
if_maddr_rlock(ifp);
|
|
CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
|
|
if (ifma->ifma_addr->sa_family != AF_LINK)
|
|
continue;
|
|
add_mac_entry(sc,
|
|
LLADDR((struct sockaddr_dl *)ifma->ifma_addr));
|
|
}
|
|
if_maddr_runlock(ifp);
|
|
}
|
|
|
|
static int
|
|
eceioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
|
|
{
|
|
struct ece_softc *sc = ifp->if_softc;
|
|
struct mii_data *mii;
|
|
struct ifreq *ifr = (struct ifreq *)data;
|
|
int mask, error = 0;
|
|
|
|
switch (cmd) {
|
|
case SIOCSIFFLAGS:
|
|
ECE_LOCK(sc);
|
|
if ((ifp->if_flags & IFF_UP) == 0 &&
|
|
ifp->if_drv_flags & IFF_DRV_RUNNING) {
|
|
ifp->if_drv_flags &= ~IFF_DRV_RUNNING;
|
|
ecestop(sc);
|
|
} else {
|
|
/* Reinitialize card on any parameter change. */
|
|
if ((ifp->if_flags & IFF_UP) &&
|
|
!(ifp->if_drv_flags & IFF_DRV_RUNNING))
|
|
ece_restart(sc);
|
|
}
|
|
ECE_UNLOCK(sc);
|
|
break;
|
|
|
|
case SIOCADDMULTI:
|
|
case SIOCDELMULTI:
|
|
ECE_LOCK(sc);
|
|
set_filter(sc);
|
|
ECE_UNLOCK(sc);
|
|
break;
|
|
|
|
case SIOCSIFMEDIA:
|
|
case SIOCGIFMEDIA:
|
|
mii = device_get_softc(sc->miibus);
|
|
error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, cmd);
|
|
break;
|
|
case SIOCSIFCAP:
|
|
mask = ifp->if_capenable ^ ifr->ifr_reqcap;
|
|
if (mask & IFCAP_VLAN_MTU) {
|
|
ECE_LOCK(sc);
|
|
ECE_UNLOCK(sc);
|
|
}
|
|
default:
|
|
error = ether_ioctl(ifp, cmd, data);
|
|
break;
|
|
}
|
|
return (error);
|
|
}
|
|
|
|
static void
|
|
ece_child_detached(device_t dev, device_t child)
|
|
{
|
|
struct ece_softc *sc;
|
|
|
|
sc = device_get_softc(dev);
|
|
if (child == sc->miibus)
|
|
sc->miibus = NULL;
|
|
}
|
|
|
|
/*
|
|
* MII bus support routines.
|
|
*/
|
|
static int
|
|
ece_miibus_readreg(device_t dev, int phy, int reg)
|
|
{
|
|
struct ece_softc *sc;
|
|
sc = device_get_softc(dev);
|
|
return (phy_read(sc, phy, reg));
|
|
}
|
|
|
|
static int
|
|
ece_miibus_writereg(device_t dev, int phy, int reg, int data)
|
|
{
|
|
struct ece_softc *sc;
|
|
sc = device_get_softc(dev);
|
|
phy_write(sc, phy, reg, data);
|
|
return (0);
|
|
}
|
|
|
|
static device_method_t ece_methods[] = {
|
|
/* Device interface */
|
|
DEVMETHOD(device_probe, ece_probe),
|
|
DEVMETHOD(device_attach, ece_attach),
|
|
DEVMETHOD(device_detach, ece_detach),
|
|
|
|
/* Bus interface */
|
|
DEVMETHOD(bus_child_detached, ece_child_detached),
|
|
|
|
/* MII interface */
|
|
DEVMETHOD(miibus_readreg, ece_miibus_readreg),
|
|
DEVMETHOD(miibus_writereg, ece_miibus_writereg),
|
|
|
|
{ 0, 0 }
|
|
};
|
|
|
|
static driver_t ece_driver = {
|
|
"ece",
|
|
ece_methods,
|
|
sizeof(struct ece_softc),
|
|
};
|
|
|
|
DRIVER_MODULE(ece, econaarm, ece_driver, ece_devclass, 0, 0);
|
|
DRIVER_MODULE(miibus, ece, miibus_driver, miibus_devclass, 0, 0);
|
|
MODULE_DEPEND(ece, miibus, 1, 1, 1);
|
|
MODULE_DEPEND(ece, ether, 1, 1, 1);
|