Add the etherswitch(4) support for TI CPSW.

Adds VLAN and port management abilities for etherswitchcfg(8).

The code is conditionally enabled for now, because it is not necessary on
single ethernet use cases.

Obtained from:	pfSense
MFC after:	2 weeks
Sponsored by:	Rubicon Communications, LLC (Netgate)
This commit is contained in:
Luiz Otavio O Souza 2016-11-24 20:14:43 +00:00
parent c5d1ccc2ed
commit a2c46b941e
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=309113
4 changed files with 317 additions and 4 deletions

View File

@ -47,6 +47,8 @@
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include "opt_cpsw.h"
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/kernel.h>
@ -78,6 +80,11 @@ __FBSDID("$FreeBSD$");
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#ifdef CPSW_ETHERSWITCH
#include <dev/etherswitch/etherswitch.h>
#include "etherswitch_if.h"
#endif
#include "if_cpswreg.h"
#include "if_cpswvar.h"
@ -142,6 +149,19 @@ static void cpsw_add_sysctls(struct cpsw_softc *);
static void cpsw_stats_collect(struct cpsw_softc *);
static int cpsw_stats_sysctl(SYSCTL_HANDLER_ARGS);
#ifdef CPSW_ETHERSWITCH
static etherswitch_info_t *cpsw_getinfo(device_t);
static int cpsw_getport(device_t, etherswitch_port_t *);
static int cpsw_setport(device_t, etherswitch_port_t *);
static int cpsw_getconf(device_t, etherswitch_conf_t *);
static int cpsw_getvgroup(device_t, etherswitch_vlangroup_t *);
static int cpsw_setvgroup(device_t, etherswitch_vlangroup_t *);
static int cpsw_readreg(device_t, int);
static int cpsw_writereg(device_t, int, int);
static int cpsw_readphy(device_t, int, int);
static int cpsw_writephy(device_t, int, int, int);
#endif
/*
* Arbitrary limit on number of segments in an mbuf to be transmitted.
* Packets with more segments than this will be defragmented before
@ -158,8 +178,23 @@ static device_method_t cpsw_methods[] = {
DEVMETHOD(device_shutdown, cpsw_shutdown),
DEVMETHOD(device_suspend, cpsw_suspend),
DEVMETHOD(device_resume, cpsw_resume),
/* Bus interface */
DEVMETHOD(bus_add_child, device_add_child_ordered),
/* OFW methods */
DEVMETHOD(ofw_bus_get_node, cpsw_get_node),
#ifdef CPSW_ETHERSWITCH
/* etherswitch interface */
DEVMETHOD(etherswitch_getinfo, cpsw_getinfo),
DEVMETHOD(etherswitch_readreg, cpsw_readreg),
DEVMETHOD(etherswitch_writereg, cpsw_writereg),
DEVMETHOD(etherswitch_readphyreg, cpsw_readphy),
DEVMETHOD(etherswitch_writephyreg, cpsw_writephy),
DEVMETHOD(etherswitch_getport, cpsw_getport),
DEVMETHOD(etherswitch_setport, cpsw_setport),
DEVMETHOD(etherswitch_getvgroup, cpsw_getvgroup),
DEVMETHOD(etherswitch_setvgroup, cpsw_setvgroup),
DEVMETHOD(etherswitch_getconf, cpsw_getconf),
#endif
DEVMETHOD_END
};
@ -194,11 +229,20 @@ static driver_t cpswp_driver = {
static devclass_t cpswp_devclass;
#ifdef CPSW_ETHERSWITCH
DRIVER_MODULE(etherswitch, cpswss, etherswitch_driver, etherswitch_devclass, 0, 0);
MODULE_DEPEND(cpswss, etherswitch, 1, 1, 1);
#endif
DRIVER_MODULE(cpsw, cpswss, cpswp_driver, cpswp_devclass, 0, 0);
DRIVER_MODULE(miibus, cpsw, miibus_driver, miibus_devclass, 0, 0);
MODULE_DEPEND(cpsw, ether, 1, 1, 1);
MODULE_DEPEND(cpsw, miibus, 1, 1, 1);
#ifdef CPSW_ETHERSWITCH
static struct cpsw_vlangroups cpsw_vgroups[CPSW_VLANS];
#endif
static uint32_t slave_mdio_addr[] = { 0x4a100200, 0x4a100300 };
static struct resource_spec irq_res_spec[] = {
@ -576,7 +620,8 @@ cpsw_init(struct cpsw_softc *sc)
cpsw_write_4(sc, CPSW_PORT_P0_CPDMA_RX_CH_MAP, 0);
/* Initialize ALE: set host port to forwarding(3). */
cpsw_write_4(sc, CPSW_ALE_PORTCTL(0), 3);
cpsw_write_4(sc, CPSW_ALE_PORTCTL(0),
ALE_PORTCTL_INGRESS | ALE_PORTCTL_FORWARD);
cpsw_write_4(sc, CPSW_SS_PTYPE, 0);
@ -851,6 +896,11 @@ cpsw_attach(device_t dev)
return (ENXIO);
}
#ifdef CPSW_ETHERSWITCH
for (i = 0; i < CPSW_VLANS; i++)
cpsw_vgroups[i].vid = -1;
#endif
/* Reset the controller. */
cpsw_reset(sc);
cpsw_init(sc);
@ -864,6 +914,7 @@ cpsw_attach(device_t dev)
return (ENXIO);
}
}
bus_generic_probe(dev);
bus_generic_attach(dev);
return (0);
@ -918,7 +969,12 @@ cpsw_detach(device_t dev)
mtx_destroy(&sc->rx.lock);
mtx_destroy(&sc->tx.lock);
return (0);
/* Detach the switch device, if present. */
error = bus_generic_detach(dev);
if (error != 0)
return (error);
return (device_delete_children(dev));
}
static phandle_t
@ -1085,6 +1141,9 @@ cpswp_init(void *arg)
static void
cpswp_init_locked(void *arg)
{
#ifdef CPSW_ETHERSWITCH
int i;
#endif
struct cpswp_softc *sc = arg;
struct ifnet *ifp;
uint32_t reg;
@ -1115,8 +1174,9 @@ cpswp_init_locked(void *arg)
reg |= CPSW_SL_MACTL_GMII_ENABLE;
cpsw_write_4(sc->swsc, CPSW_SL_MACCONTROL(sc->unit), reg);
/* Initialize ALE: set port to forwarding(3), initialize addrs */
cpsw_write_4(sc->swsc, CPSW_ALE_PORTCTL(sc->unit + 1), 3);
/* Initialize ALE: set port to forwarding, initialize addrs */
cpsw_write_4(sc->swsc, CPSW_ALE_PORTCTL(sc->unit + 1),
ALE_PORTCTL_INGRESS | ALE_PORTCTL_FORWARD);
cpswp_ale_update_addresses(sc, 1);
if (sc->swsc->dualemac) {
@ -1127,6 +1187,14 @@ cpswp_init_locked(void *arg)
(1 << (sc->unit + 1)) | (1 << 0), /* Member list */
(1 << (sc->unit + 1)) | (1 << 0), /* Untagged egress */
(1 << (sc->unit + 1)) | (1 << 0), 0); /* mcast reg flood */
#ifdef CPSW_ETHERSWITCH
for (i = 0; i < CPSW_VLANS; i++) {
if (cpsw_vgroups[i].vid != -1)
continue;
cpsw_vgroups[i].vid = sc->vlan;
break;
}
#endif
}
mii_mediachg(sc->mii);
@ -2699,3 +2767,229 @@ cpsw_add_sysctls(struct cpsw_softc *sc)
CTLFLAG_RD, NULL, "Watchdog Statistics");
cpsw_add_watchdog_sysctls(ctx, node, sc);
}
#ifdef CPSW_ETHERSWITCH
static etherswitch_info_t etherswitch_info = {
.es_nports = CPSW_PORTS + 1,
.es_nvlangroups = CPSW_VLANS,
.es_name = "TI Common Platform Ethernet Switch (CPSW)",
.es_vlan_caps = ETHERSWITCH_VLAN_DOT1Q,
};
static etherswitch_info_t *
cpsw_getinfo(device_t dev)
{
return (&etherswitch_info);
}
static int
cpsw_getport(device_t dev, etherswitch_port_t *p)
{
int err;
struct cpsw_softc *sc;
struct cpswp_softc *psc;
struct ifmediareq *ifmr;
uint32_t reg;
if (p->es_port < 0 || p->es_port > CPSW_PORTS)
return (ENXIO);
err = 0;
sc = device_get_softc(dev);
if (p->es_port == CPSW_CPU_PORT) {
p->es_flags |= ETHERSWITCH_PORT_CPU;
ifmr = &p->es_ifmr;
ifmr->ifm_current = ifmr->ifm_active =
IFM_ETHER | IFM_1000_T | IFM_FDX;
ifmr->ifm_mask = 0;
ifmr->ifm_status = IFM_ACTIVE | IFM_AVALID;
ifmr->ifm_count = 0;
} else {
psc = device_get_softc(sc->port[p->es_port - 1].dev);
err = ifmedia_ioctl(psc->ifp, &p->es_ifr,
&psc->mii->mii_media, SIOCGIFMEDIA);
}
reg = cpsw_read_4(sc, CPSW_PORT_P_VLAN(p->es_port));
p->es_pvid = reg & ETHERSWITCH_VID_MASK;
reg = cpsw_read_4(sc, CPSW_ALE_PORTCTL(p->es_port));
if (reg & ALE_PORTCTL_DROP_UNTAGGED)
p->es_flags |= ETHERSWITCH_PORT_DROPUNTAGGED;
if (reg & ALE_PORTCTL_INGRESS)
p->es_flags |= ETHERSWITCH_PORT_INGRESS;
return (err);
}
static int
cpsw_setport(device_t dev, etherswitch_port_t *p)
{
struct cpsw_softc *sc;
struct cpswp_softc *psc;
struct ifmedia *ifm;
uint32_t reg;
if (p->es_port < 0 || p->es_port > CPSW_PORTS)
return (ENXIO);
sc = device_get_softc(dev);
if (p->es_pvid != 0) {
cpsw_write_4(sc, CPSW_PORT_P_VLAN(p->es_port),
p->es_pvid & ETHERSWITCH_VID_MASK);
}
reg = cpsw_read_4(sc, CPSW_ALE_PORTCTL(p->es_port));
if (p->es_flags & ETHERSWITCH_PORT_DROPUNTAGGED)
reg |= ALE_PORTCTL_DROP_UNTAGGED;
else
reg &= ~ALE_PORTCTL_DROP_UNTAGGED;
if (p->es_flags & ETHERSWITCH_PORT_INGRESS)
reg |= ALE_PORTCTL_INGRESS;
else
reg &= ~ALE_PORTCTL_INGRESS;
cpsw_write_4(sc, CPSW_ALE_PORTCTL(p->es_port), reg);
/* CPU port does not allow media settings. */
if (p->es_port == CPSW_CPU_PORT)
return (0);
psc = device_get_softc(sc->port[p->es_port - 1].dev);
ifm = &psc->mii->mii_media;
return (ifmedia_ioctl(psc->ifp, &p->es_ifr, ifm, SIOCSIFMEDIA));
}
static int
cpsw_getconf(device_t dev, etherswitch_conf_t *conf)
{
/* Return the VLAN mode. */
conf->cmd = ETHERSWITCH_CONF_VLAN_MODE;
conf->vlan_mode = ETHERSWITCH_VLAN_DOT1Q;
return (0);
}
static int
cpsw_getvgroup(device_t dev, etherswitch_vlangroup_t *vg)
{
int i, vid;
uint32_t ale_entry[3];
struct cpsw_softc *sc;
sc = device_get_softc(dev);
if (vg->es_vlangroup >= CPSW_VLANS)
return (EINVAL);
vg->es_vid = 0;
vid = cpsw_vgroups[vg->es_vlangroup].vid;
if (vid == -1)
return (0);
for (i = 0; i < CPSW_MAX_ALE_ENTRIES; i++) {
cpsw_ale_read_entry(sc, i, ale_entry);
if (ALE_TYPE(ale_entry) != ALE_TYPE_VLAN)
continue;
if (vid != ALE_VLAN(ale_entry))
continue;
vg->es_fid = 0;
vg->es_vid = ALE_VLAN(ale_entry) | ETHERSWITCH_VID_VALID;
vg->es_member_ports = ALE_VLAN_MEMBERS(ale_entry);
vg->es_untagged_ports = ALE_VLAN_UNTAG(ale_entry);
}
return (0);
}
static void
cpsw_remove_vlan(struct cpsw_softc *sc, int vlan)
{
int i;
uint32_t ale_entry[3];
for (i = 0; i < CPSW_MAX_ALE_ENTRIES; i++) {
cpsw_ale_read_entry(sc, i, ale_entry);
if (ALE_TYPE(ale_entry) != ALE_TYPE_VLAN)
continue;
if (vlan != ALE_VLAN(ale_entry))
continue;
ale_entry[0] = ale_entry[1] = ale_entry[2] = 0;
cpsw_ale_write_entry(sc, i, ale_entry);
break;
}
}
static int
cpsw_setvgroup(device_t dev, etherswitch_vlangroup_t *vg)
{
int i;
struct cpsw_softc *sc;
sc = device_get_softc(dev);
for (i = 0; i < CPSW_VLANS; i++) {
/* Is this Vlan ID in use by another vlangroup ? */
if (vg->es_vlangroup != i && cpsw_vgroups[i].vid == vg->es_vid)
return (EINVAL);
}
if (vg->es_vid == 0) {
if (cpsw_vgroups[vg->es_vlangroup].vid == -1)
return (0);
cpsw_remove_vlan(sc, cpsw_vgroups[vg->es_vlangroup].vid);
cpsw_vgroups[vg->es_vlangroup].vid = -1;
vg->es_untagged_ports = 0;
vg->es_member_ports = 0;
vg->es_vid = 0;
return (0);
}
vg->es_vid &= ETHERSWITCH_VID_MASK;
vg->es_member_ports &= CPSW_PORTS_MASK;
vg->es_untagged_ports &= CPSW_PORTS_MASK;
if (cpsw_vgroups[vg->es_vlangroup].vid != -1 &&
cpsw_vgroups[vg->es_vlangroup].vid != vg->es_vid)
return (EINVAL);
cpsw_vgroups[vg->es_vlangroup].vid = vg->es_vid;
cpsw_ale_update_vlan_table(sc, vg->es_vid, vg->es_member_ports,
vg->es_untagged_ports, vg->es_member_ports, 0);
return (0);
}
static int
cpsw_readreg(device_t dev, int addr)
{
/* Not supported. */
return (0);
}
static int
cpsw_writereg(device_t dev, int addr, int value)
{
/* Not supported. */
return (0);
}
static int
cpsw_readphy(device_t dev, int phy, int reg)
{
/* Not supported. */
return (0);
}
static int
cpsw_writephy(device_t dev, int phy, int reg, int data)
{
/* Not supported. */
return (0);
}
#endif

View File

@ -106,6 +106,14 @@
#define ALE_VLAN_UNTAG(_a) ((_a[0] >> 24) & 7)
#define ALE_VLAN_MEMBERS(_a) (_a[0] & 7)
#define CPSW_ALE_PORTCTL(p) (CPSW_ALE_OFFSET + 0x40 + ((p) * 0x04))
#define ALE_PORTCTL_NO_SA_UPDATE (1 << 5)
#define ALE_PORTCTL_NO_LEARN (1 << 4)
#define ALE_PORTCTL_INGRESS (1 << 3)
#define ALE_PORTCTL_DROP_UNTAGGED (1 << 2)
#define ALE_PORTCTL_FORWARD 3
#define ALE_PORTCTL_LEARN 2
#define ALE_PORTCTL_BLOCKED 1
#define ALE_PORTCTL_DISABLED 0
/* SL1 is at 0x0D80, SL2 is at 0x0DC0 */
#define CPSW_SL_OFFSET 0x0D80

View File

@ -40,6 +40,16 @@
#define CPSW_SYSCTL_COUNT 34
#ifdef CPSW_ETHERSWITCH
#define CPSW_CPU_PORT 0
#define CPSW_PORTS_MASK 0x7
#define CPSW_VLANS 128 /* Arbitrary number. */
struct cpsw_vlangroups {
int vid;
};
#endif
struct cpsw_slot {
uint32_t bd_offset; /* Offset of corresponding BD within CPPI RAM. */
bus_dmamap_t dmamap;

View File

@ -7,6 +7,7 @@ ARM_MANY_BOARD opt_global.h
NKPT2PG opt_pmap.h
ARM_WANT_TP_ADDRESS opt_global.h
COUNTS_PER_SEC opt_timer.h
CPSW_ETHERSWITCH opt_cpsw.h
CPU_ARM9 opt_global.h
CPU_ARM9E opt_global.h
CPU_ARM1176 opt_global.h