f05957f7c6
using miibus, since for some devices that use multiple addresses on the bus, going through miibus may be unclear, and for devices that are not standard MII PHYs, miibus may throw a fit, necessitating complicated interfaces to fake the interface that it expects during probe/attach. o) Make the mv88e61xx SMI interface in octe attach a PHY directly and fix some mistakes in the code that resulted from trying too hard to present a nice interface to miibus. o) Add a PHY driver for the mv88e61xx. If attached (it is optional in kernel compiles so the default behavior of having a dumb switch is preserved) it will place the switch in a VLAN-tagging mode such that each physical port has a VLAN associated with it and interfaces for the VLANs can be created to address or bridge between them. XXX It would be nice for this to be part of a single module including the SMI interface, and for it to fit into a generic switch configuration framework and for it to use DSA rather than VLANs, but this is a start and gives some sense of the parameters of such frameworks that are not currently present in FreeBSD. In lieu of a switch configuration interface, per-port media status and VLAN settings are in a sysctl tree. XXX There may be some minor nits remaining in the handling of broadcast, multicast and unknown destination traffic. It would also be nice to go through and replace the few remaining magic numbers with macros at some point in the future. XXX This has only been tested with the MV88E6161, but it should work with minimal or no modification on related switches, so support for probing them was included. Thanks to Pat Saavedra of TELoIP and Rafal Jaworowski of Semihalf for their assistance in understanding the switch chipset.
631 lines
17 KiB
C
631 lines
17 KiB
C
/*-
|
|
* Copyright (c) 2010 Juli Mallett <jmallett@FreeBSD.org>
|
|
* 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 THE 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 THE 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.
|
|
*
|
|
* $FreeBSD$
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
/*
|
|
* Driver for the Marvell 88E61xx family of switch PHYs
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/module.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/sysctl.h>
|
|
|
|
#include <net/ethernet.h>
|
|
#include <net/if.h>
|
|
#include <net/if_media.h>
|
|
|
|
#include "miibus_if.h"
|
|
|
|
#include "mv88e61xxphyreg.h"
|
|
|
|
struct mv88e61xxphy_softc;
|
|
|
|
struct mv88e61xxphy_port_softc {
|
|
struct mv88e61xxphy_softc *sc_switch;
|
|
unsigned sc_port;
|
|
unsigned sc_domain;
|
|
unsigned sc_vlan;
|
|
unsigned sc_priority;
|
|
unsigned sc_flags;
|
|
};
|
|
|
|
#define MV88E61XXPHY_PORT_FLAG_VTU_UPDATE (0x0001)
|
|
|
|
struct mv88e61xxphy_softc {
|
|
device_t sc_dev;
|
|
struct mv88e61xxphy_port_softc sc_ports[MV88E61XX_PORTS];
|
|
};
|
|
|
|
enum mv88e61xxphy_vtu_membership_type {
|
|
MV88E61XXPHY_VTU_UNMODIFIED,
|
|
MV88E61XXPHY_VTU_UNTAGGED,
|
|
MV88E61XXPHY_VTU_TAGGED,
|
|
MV88E61XXPHY_VTU_DISCARDED,
|
|
};
|
|
|
|
enum mv88e61xxphy_sysctl_link_type {
|
|
MV88E61XXPHY_LINK_SYSCTL_DUPLEX,
|
|
MV88E61XXPHY_LINK_SYSCTL_LINK,
|
|
MV88E61XXPHY_LINK_SYSCTL_MEDIA,
|
|
};
|
|
|
|
enum mv88e61xxphy_sysctl_port_type {
|
|
MV88E61XXPHY_PORT_SYSCTL_DOMAIN,
|
|
MV88E61XXPHY_PORT_SYSCTL_VLAN,
|
|
MV88E61XXPHY_PORT_SYSCTL_PRIORITY,
|
|
};
|
|
|
|
/*
|
|
* Register access macros.
|
|
*/
|
|
#define MV88E61XX_READ(sc, phy, reg) \
|
|
MIIBUS_READREG(device_get_parent((sc)->sc_dev), (phy), (reg))
|
|
|
|
#define MV88E61XX_WRITE(sc, phy, reg, val) \
|
|
MIIBUS_WRITEREG(device_get_parent((sc)->sc_dev), (phy), (reg), (val))
|
|
|
|
#define MV88E61XX_READ_PORT(psc, reg) \
|
|
MV88E61XX_READ((psc)->sc_switch, MV88E61XX_PORT((psc)->sc_port), (reg))
|
|
|
|
#define MV88E61XX_WRITE_PORT(psc, reg, val) \
|
|
MV88E61XX_WRITE((psc)->sc_switch, MV88E61XX_PORT((psc)->sc_port), (reg), (val))
|
|
|
|
static int mv88e61xxphy_probe(device_t);
|
|
static int mv88e61xxphy_attach(device_t);
|
|
|
|
static void mv88e61xxphy_init(struct mv88e61xxphy_softc *);
|
|
static void mv88e61xxphy_init_port(struct mv88e61xxphy_port_softc *);
|
|
static void mv88e61xxphy_init_vtu(struct mv88e61xxphy_softc *);
|
|
static int mv88e61xxphy_sysctl_link_proc(SYSCTL_HANDLER_ARGS);
|
|
static int mv88e61xxphy_sysctl_port_proc(SYSCTL_HANDLER_ARGS);
|
|
static void mv88e61xxphy_vtu_load(struct mv88e61xxphy_softc *, uint16_t);
|
|
static void mv88e61xxphy_vtu_set_membership(struct mv88e61xxphy_softc *, unsigned, enum mv88e61xxphy_vtu_membership_type);
|
|
static void mv88e61xxphy_vtu_wait(struct mv88e61xxphy_softc *);
|
|
|
|
static int
|
|
mv88e61xxphy_probe(device_t dev)
|
|
{
|
|
uint16_t val;
|
|
|
|
val = MIIBUS_READREG(device_get_parent(dev), MV88E61XX_PORT(0),
|
|
MV88E61XX_PORT_REVISION);
|
|
switch (val >> 4) {
|
|
case 0x121:
|
|
device_set_desc(dev, "Marvell Link Street 88E6123 3-Port Gigabit Switch");
|
|
return (0);
|
|
case 0x161:
|
|
device_set_desc(dev, "Marvell Link Street 88E6161 6-Port Gigabit Switch");
|
|
return (0);
|
|
case 0x165:
|
|
device_set_desc(dev, "Marvell Link Street 88E6161 6-Port Advanced Gigabit Switch");
|
|
return (0);
|
|
default:
|
|
return (ENXIO);
|
|
}
|
|
}
|
|
|
|
static int
|
|
mv88e61xxphy_attach(device_t dev)
|
|
{
|
|
char portbuf[] = "N";
|
|
struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(dev);
|
|
struct sysctl_oid *tree = device_get_sysctl_tree(dev);
|
|
struct sysctl_oid_list *child = SYSCTL_CHILDREN(tree);
|
|
struct sysctl_oid *port_node, *portN_node;
|
|
struct sysctl_oid_list *port_tree, *portN_tree;
|
|
struct mv88e61xxphy_softc *sc;
|
|
unsigned port;
|
|
|
|
sc = device_get_softc(dev);
|
|
sc->sc_dev = dev;
|
|
|
|
/*
|
|
* Initialize port softcs.
|
|
*/
|
|
for (port = 0; port < MV88E61XX_PORTS; port++) {
|
|
struct mv88e61xxphy_port_softc *psc;
|
|
|
|
psc = &sc->sc_ports[port];
|
|
psc->sc_switch = sc;
|
|
psc->sc_port = port;
|
|
psc->sc_domain = 0; /* One broadcast domain by default. */
|
|
psc->sc_vlan = port + 1; /* Tag VLANs by default. */
|
|
psc->sc_priority = 0; /* No default special priority. */
|
|
psc->sc_flags = 0;
|
|
}
|
|
|
|
/*
|
|
* Add per-port sysctl tree/handlers.
|
|
*/
|
|
port_node = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "port",
|
|
CTLFLAG_RD, NULL, "Switch Ports");
|
|
port_tree = SYSCTL_CHILDREN(port_node);
|
|
for (port = 0; port < MV88E61XX_PORTS; port++) {
|
|
struct mv88e61xxphy_port_softc *psc;
|
|
|
|
psc = &sc->sc_ports[port];
|
|
|
|
portbuf[0] = '0' + port;
|
|
portN_node = SYSCTL_ADD_NODE(ctx, port_tree, OID_AUTO, portbuf,
|
|
CTLFLAG_RD, NULL, "Switch Port");
|
|
portN_tree = SYSCTL_CHILDREN(portN_node);
|
|
|
|
SYSCTL_ADD_PROC(ctx, portN_tree, OID_AUTO, "duplex",
|
|
CTLFLAG_RD | CTLTYPE_INT, psc,
|
|
MV88E61XXPHY_LINK_SYSCTL_DUPLEX,
|
|
mv88e61xxphy_sysctl_link_proc, "IU",
|
|
"Media duplex status (0 = half duplex; 1 = full duplex)");
|
|
|
|
SYSCTL_ADD_PROC(ctx, portN_tree, OID_AUTO, "link",
|
|
CTLFLAG_RD | CTLTYPE_INT, psc,
|
|
MV88E61XXPHY_LINK_SYSCTL_LINK,
|
|
mv88e61xxphy_sysctl_link_proc, "IU",
|
|
"Link status (0 = down; 1 = up)");
|
|
|
|
SYSCTL_ADD_PROC(ctx, portN_tree, OID_AUTO, "media",
|
|
CTLFLAG_RD | CTLTYPE_INT, psc,
|
|
MV88E61XXPHY_LINK_SYSCTL_MEDIA,
|
|
mv88e61xxphy_sysctl_link_proc, "IU",
|
|
"Media speed (0 = unknown; 10 = 10Mbps; 100 = 100Mbps; 1000 = 1Gbps)");
|
|
|
|
SYSCTL_ADD_PROC(ctx, portN_tree, OID_AUTO, "domain",
|
|
CTLFLAG_RW | CTLTYPE_INT, psc,
|
|
MV88E61XXPHY_PORT_SYSCTL_DOMAIN,
|
|
mv88e61xxphy_sysctl_port_proc, "IU",
|
|
"Broadcast domain (ports can only talk to other ports in the same domain)");
|
|
|
|
SYSCTL_ADD_PROC(ctx, portN_tree, OID_AUTO, "vlan",
|
|
CTLFLAG_RW | CTLTYPE_INT, psc,
|
|
MV88E61XXPHY_PORT_SYSCTL_VLAN,
|
|
mv88e61xxphy_sysctl_port_proc, "IU",
|
|
"Tag packets from/for this port with a given VLAN.");
|
|
|
|
SYSCTL_ADD_PROC(ctx, portN_tree, OID_AUTO, "priority",
|
|
CTLFLAG_RW | CTLTYPE_INT, psc,
|
|
MV88E61XXPHY_PORT_SYSCTL_PRIORITY,
|
|
mv88e61xxphy_sysctl_port_proc, "IU",
|
|
"Default packet priority for this port.");
|
|
}
|
|
|
|
mv88e61xxphy_init(sc);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
mv88e61xxphy_init(struct mv88e61xxphy_softc *sc)
|
|
{
|
|
unsigned port;
|
|
uint16_t val;
|
|
unsigned i;
|
|
|
|
/* Disable all ports. */
|
|
for (port = 0; port < MV88E61XX_PORTS; port++) {
|
|
struct mv88e61xxphy_port_softc *psc;
|
|
|
|
psc = &sc->sc_ports[port];
|
|
|
|
val = MV88E61XX_READ_PORT(psc, MV88E61XX_PORT_CONTROL);
|
|
val &= ~0x3;
|
|
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_CONTROL, val);
|
|
}
|
|
|
|
DELAY(2000);
|
|
|
|
/* Reset the switch. */
|
|
MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_CONTROL, 0xc400);
|
|
for (i = 0; i < 100; i++) {
|
|
val = MV88E61XX_READ(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_STATUS);
|
|
if ((val & 0xc800) == 0xc800)
|
|
break;
|
|
DELAY(10);
|
|
}
|
|
if (i == 100) {
|
|
device_printf(sc->sc_dev, "%s: switch reset timed out.\n", __func__);
|
|
return;
|
|
}
|
|
|
|
/* Disable PPU. */
|
|
MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_CONTROL, 0x0000);
|
|
|
|
/* Configure host port and send monitor frames to it. */
|
|
MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_MONITOR,
|
|
(MV88E61XX_HOST_PORT << 12) | (MV88E61XX_HOST_PORT << 8) |
|
|
(MV88E61XX_HOST_PORT << 4));
|
|
|
|
/* Disable remote management. */
|
|
MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_REMOTE_MGMT, 0x0000);
|
|
|
|
/* Send all specifically-addressed frames to the host port. */
|
|
MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL2, MV88E61XX_GLOBAL2_MANAGE_2X, 0xffff);
|
|
MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL2, MV88E61XX_GLOBAL2_MANAGE_0X, 0xffff);
|
|
|
|
/* Remove provider-supplied tag and use it for switching. */
|
|
MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL2, MV88E61XX_GLOBAL2_CONTROL2,
|
|
MV88E61XX_GLOBAL2_CONTROL2_REMOVE_PTAG);
|
|
|
|
/* Configure all ports. */
|
|
for (port = 0; port < MV88E61XX_PORTS; port++) {
|
|
struct mv88e61xxphy_port_softc *psc;
|
|
|
|
psc = &sc->sc_ports[port];
|
|
mv88e61xxphy_init_port(psc);
|
|
}
|
|
|
|
/* Reprogram VLAN table (VTU.) */
|
|
mv88e61xxphy_init_vtu(sc);
|
|
|
|
/* Enable all ports. */
|
|
for (port = 0; port < MV88E61XX_PORTS; port++) {
|
|
struct mv88e61xxphy_port_softc *psc;
|
|
|
|
psc = &sc->sc_ports[port];
|
|
|
|
val = MV88E61XX_READ_PORT(psc, MV88E61XX_PORT_CONTROL);
|
|
val |= 0x3;
|
|
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_CONTROL, val);
|
|
}
|
|
}
|
|
|
|
static void
|
|
mv88e61xxphy_init_port(struct mv88e61xxphy_port_softc *psc)
|
|
{
|
|
struct mv88e61xxphy_softc *sc;
|
|
unsigned allow_mask;
|
|
|
|
sc = psc->sc_switch;
|
|
|
|
/* Set media type and flow control. */
|
|
if (psc->sc_port != MV88E61XX_HOST_PORT) {
|
|
/* Don't force any media type or flow control. */
|
|
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_FORCE_MAC, 0x0003);
|
|
} else {
|
|
/* Make CPU port 1G FDX. */
|
|
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_FORCE_MAC, 0x003e);
|
|
}
|
|
|
|
/* Don't limit flow control pauses. */
|
|
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_PAUSE_CONTROL, 0x0000);
|
|
|
|
/* Set various port functions per Linux. */
|
|
if (psc->sc_port != MV88E61XX_HOST_PORT) {
|
|
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_CONTROL, 0x04bc);
|
|
} else {
|
|
/*
|
|
* Send frames for unknown unicast and multicast groups to
|
|
* host, too.
|
|
*/
|
|
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_CONTROL, 0x063f);
|
|
}
|
|
|
|
if (psc->sc_port != MV88E61XX_HOST_PORT) {
|
|
/* Disable trunking. */
|
|
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_CONTROL2, 0x0000);
|
|
} else {
|
|
/* Disable trunking and send learn messages to host. */
|
|
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_CONTROL2, 0x8000);
|
|
}
|
|
|
|
/*
|
|
* Port-based VLAN map; isolates MAC tables and forces ports to talk
|
|
* only to the host.
|
|
*
|
|
* Always allow the host to send to all ports and allow all ports to
|
|
* send to the host.
|
|
*/
|
|
if (psc->sc_port != MV88E61XX_HOST_PORT) {
|
|
allow_mask = 1 << MV88E61XX_HOST_PORT;
|
|
} else {
|
|
allow_mask = (1 << MV88E61XX_PORTS) - 1;
|
|
allow_mask &= ~(1 << MV88E61XX_HOST_PORT);
|
|
}
|
|
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_VLAN_MAP,
|
|
(psc->sc_domain << 12) | allow_mask);
|
|
|
|
/* VLAN tagging. Set default priority and VLAN tag (or none.) */
|
|
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_VLAN,
|
|
(psc->sc_priority << 14) | psc->sc_vlan);
|
|
|
|
if (psc->sc_port == MV88E61XX_HOST_PORT) {
|
|
/* Set provider ingress tag. */
|
|
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_PROVIDER_PROTO,
|
|
ETHERTYPE_VLAN);
|
|
|
|
/* Set provider egress tag. */
|
|
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_ETHER_PROTO,
|
|
ETHERTYPE_VLAN);
|
|
|
|
/* Use secure 802.1q mode and accept only tagged frames. */
|
|
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_FILTER,
|
|
MV88E61XX_PORT_FILTER_MAP_DEST |
|
|
MV88E61XX_PORT_FILTER_8021Q_SECURE |
|
|
MV88E61XX_PORT_FILTER_DISCARD_UNTAGGED);
|
|
} else {
|
|
/* Don't allow tagged frames. */
|
|
MV88E61XX_WRITE_PORT(psc, MV88E61XX_PORT_FILTER,
|
|
MV88E61XX_PORT_FILTER_MAP_DEST |
|
|
MV88E61XX_PORT_FILTER_DISCARD_TAGGED);
|
|
}
|
|
}
|
|
|
|
static void
|
|
mv88e61xxphy_init_vtu(struct mv88e61xxphy_softc *sc)
|
|
{
|
|
unsigned port;
|
|
|
|
/*
|
|
* Start flush of the VTU.
|
|
*/
|
|
mv88e61xxphy_vtu_wait(sc);
|
|
MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_VTU_OP,
|
|
MV88E61XX_GLOBAL_VTU_OP_BUSY | MV88E61XX_GLOBAL_VTU_OP_OP_FLUSH);
|
|
|
|
/*
|
|
* Queue each port's VLAN to be programmed.
|
|
*/
|
|
for (port = 0; port < MV88E61XX_PORTS; port++) {
|
|
struct mv88e61xxphy_port_softc *psc;
|
|
|
|
psc = &sc->sc_ports[port];
|
|
psc->sc_flags &= ~MV88E61XXPHY_PORT_FLAG_VTU_UPDATE;
|
|
if (psc->sc_vlan == 0)
|
|
continue;
|
|
psc->sc_flags |= MV88E61XXPHY_PORT_FLAG_VTU_UPDATE;
|
|
}
|
|
|
|
/*
|
|
* Program each VLAN that is in use.
|
|
*/
|
|
for (port = 0; port < MV88E61XX_PORTS; port++) {
|
|
struct mv88e61xxphy_port_softc *psc;
|
|
|
|
psc = &sc->sc_ports[port];
|
|
if ((psc->sc_flags & MV88E61XXPHY_PORT_FLAG_VTU_UPDATE) == 0)
|
|
continue;
|
|
mv88e61xxphy_vtu_load(sc, psc->sc_vlan);
|
|
}
|
|
|
|
/*
|
|
* Wait for last pending VTU operation to complete.
|
|
*/
|
|
mv88e61xxphy_vtu_wait(sc);
|
|
}
|
|
|
|
static int
|
|
mv88e61xxphy_sysctl_link_proc(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
struct mv88e61xxphy_port_softc *psc = arg1;
|
|
enum mv88e61xxphy_sysctl_link_type type = arg2;
|
|
uint16_t val;
|
|
unsigned out;
|
|
|
|
val = MV88E61XX_READ_PORT(psc, MV88E61XX_PORT_STATUS);
|
|
switch (type) {
|
|
case MV88E61XXPHY_LINK_SYSCTL_DUPLEX:
|
|
if ((val & MV88E61XX_PORT_STATUS_DUPLEX) != 0)
|
|
out = 1;
|
|
else
|
|
out = 0;
|
|
break;
|
|
case MV88E61XXPHY_LINK_SYSCTL_LINK:
|
|
if ((val & MV88E61XX_PORT_STATUS_LINK) != 0)
|
|
out = 1;
|
|
else
|
|
out = 0;
|
|
break;
|
|
case MV88E61XXPHY_LINK_SYSCTL_MEDIA:
|
|
switch (val & MV88E61XX_PORT_STATUS_MEDIA) {
|
|
case MV88E61XX_PORT_STATUS_MEDIA_10M:
|
|
out = 10;
|
|
break;
|
|
case MV88E61XX_PORT_STATUS_MEDIA_100M:
|
|
out = 100;
|
|
break;
|
|
case MV88E61XX_PORT_STATUS_MEDIA_1G:
|
|
out = 1000;
|
|
break;
|
|
default:
|
|
out = 0;
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
return (EINVAL);
|
|
}
|
|
return (sysctl_handle_int(oidp, NULL, out, req));
|
|
}
|
|
|
|
static int
|
|
mv88e61xxphy_sysctl_port_proc(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
struct mv88e61xxphy_port_softc *psc = arg1;
|
|
enum mv88e61xxphy_sysctl_port_type type = arg2;
|
|
struct mv88e61xxphy_softc *sc = psc->sc_switch;
|
|
unsigned max, val, *valp;
|
|
int error;
|
|
|
|
switch (type) {
|
|
case MV88E61XXPHY_PORT_SYSCTL_DOMAIN:
|
|
valp = &psc->sc_domain;
|
|
max = 0xf;
|
|
break;
|
|
case MV88E61XXPHY_PORT_SYSCTL_VLAN:
|
|
valp = &psc->sc_vlan;
|
|
max = 0x1000;
|
|
break;
|
|
case MV88E61XXPHY_PORT_SYSCTL_PRIORITY:
|
|
valp = &psc->sc_priority;
|
|
max = 3;
|
|
break;
|
|
default:
|
|
return (EINVAL);
|
|
}
|
|
|
|
val = *valp;
|
|
error = sysctl_handle_int(oidp, &val, 0, req);
|
|
if (error != 0 || req->newptr == NULL)
|
|
return (error);
|
|
|
|
/* Bounds check value. */
|
|
if (val >= max)
|
|
return (EINVAL);
|
|
|
|
/* Reinitialize switch with new value. */
|
|
*valp = val;
|
|
mv88e61xxphy_init(sc);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
mv88e61xxphy_vtu_load(struct mv88e61xxphy_softc *sc, uint16_t vid)
|
|
{
|
|
unsigned port;
|
|
|
|
/*
|
|
* Wait for previous operation to complete.
|
|
*/
|
|
mv88e61xxphy_vtu_wait(sc);
|
|
|
|
/*
|
|
* Set VID.
|
|
*/
|
|
MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_VTU_VID,
|
|
MV88E61XX_GLOBAL_VTU_VID_VALID | vid);
|
|
|
|
/*
|
|
* Add ports to this VTU.
|
|
*/
|
|
for (port = 0; port < MV88E61XX_PORTS; port++) {
|
|
struct mv88e61xxphy_port_softc *psc;
|
|
|
|
psc = &sc->sc_ports[port];
|
|
if (psc->sc_vlan == vid) {
|
|
/*
|
|
* Send this port its VLAN traffic untagged.
|
|
*/
|
|
psc->sc_flags &= ~MV88E61XXPHY_PORT_FLAG_VTU_UPDATE;
|
|
mv88e61xxphy_vtu_set_membership(sc, port, MV88E61XXPHY_VTU_UNTAGGED);
|
|
} else if (psc->sc_port == MV88E61XX_HOST_PORT) {
|
|
/*
|
|
* The host sees all VLANs tagged.
|
|
*/
|
|
mv88e61xxphy_vtu_set_membership(sc, port, MV88E61XXPHY_VTU_TAGGED);
|
|
} else {
|
|
/*
|
|
* This port isn't on this VLAN.
|
|
*/
|
|
mv88e61xxphy_vtu_set_membership(sc, port, MV88E61XXPHY_VTU_DISCARDED);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Start adding this entry.
|
|
*/
|
|
MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_VTU_OP,
|
|
MV88E61XX_GLOBAL_VTU_OP_BUSY |
|
|
MV88E61XX_GLOBAL_VTU_OP_OP_VTU_LOAD);
|
|
}
|
|
|
|
static void
|
|
mv88e61xxphy_vtu_set_membership(struct mv88e61xxphy_softc *sc, unsigned port,
|
|
enum mv88e61xxphy_vtu_membership_type type)
|
|
{
|
|
unsigned shift, reg;
|
|
uint16_t bits;
|
|
uint16_t val;
|
|
|
|
switch (type) {
|
|
case MV88E61XXPHY_VTU_UNMODIFIED:
|
|
bits = 0;
|
|
break;
|
|
case MV88E61XXPHY_VTU_UNTAGGED:
|
|
bits = 1;
|
|
break;
|
|
case MV88E61XXPHY_VTU_TAGGED:
|
|
bits = 2;
|
|
break;
|
|
case MV88E61XXPHY_VTU_DISCARDED:
|
|
bits = 3;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
if (port < 4) {
|
|
reg = MV88E61XX_GLOBAL_VTU_DATA_P0P3;
|
|
shift = port * 4;
|
|
} else {
|
|
reg = MV88E61XX_GLOBAL_VTU_DATA_P4P5;
|
|
shift = (port - 4) * 4;
|
|
}
|
|
|
|
val = MV88E61XX_READ(sc, MV88E61XX_GLOBAL, reg);
|
|
val |= bits << shift;
|
|
MV88E61XX_WRITE(sc, MV88E61XX_GLOBAL, reg, val);
|
|
}
|
|
|
|
static void
|
|
mv88e61xxphy_vtu_wait(struct mv88e61xxphy_softc *sc)
|
|
{
|
|
uint16_t val;
|
|
|
|
for (;;) {
|
|
val = MV88E61XX_READ(sc, MV88E61XX_GLOBAL, MV88E61XX_GLOBAL_VTU_OP);
|
|
if ((val & MV88E61XX_GLOBAL_VTU_OP_BUSY) == 0)
|
|
return;
|
|
}
|
|
}
|
|
|
|
static device_method_t mv88e61xxphy_methods[] = {
|
|
/* device interface */
|
|
DEVMETHOD(device_probe, mv88e61xxphy_probe),
|
|
DEVMETHOD(device_attach, mv88e61xxphy_attach),
|
|
DEVMETHOD(device_detach, bus_generic_detach),
|
|
DEVMETHOD(device_shutdown, bus_generic_shutdown),
|
|
|
|
{ 0, 0 }
|
|
};
|
|
|
|
static devclass_t mv88e61xxphy_devclass;
|
|
|
|
static driver_t mv88e61xxphy_driver = {
|
|
"mv88e61xxphy",
|
|
mv88e61xxphy_methods,
|
|
sizeof(struct mv88e61xxphy_softc)
|
|
};
|
|
|
|
DRIVER_MODULE(mv88e61xxphy, octe, mv88e61xxphy_driver, mv88e61xxphy_devclass, 0, 0);
|