#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <sys/module.h>
#include <sys/rman.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/gpio.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/smp.h>
#include <sys/proc.h>
#include <sys/sched.h>
#include <sys/queue.h>
#include <sys/taskqueue.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_vlan_var.h>
#include <net/if_media.h>
#include <net/ethernet.h>
#include <net/if_types.h>
#include <machine/bus.h>
#include <machine/resource.h>
#include <machine/atomic.h>
#include <dev/gpio/gpiobusvar.h>
#include <dev/fdt/fdt_common.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <dev/qcom_ess_edma/qcom_ess_edma_var.h>
#include <dev/qcom_ess_edma/qcom_ess_edma_reg.h>
#include <dev/qcom_ess_edma/qcom_ess_edma_hw.h>
#include <dev/qcom_ess_edma/qcom_ess_edma_desc.h>
#include <dev/qcom_ess_edma/qcom_ess_edma_rx.h>
#include <dev/qcom_ess_edma/qcom_ess_edma_tx.h>
#include <dev/qcom_ess_edma/qcom_ess_edma_debug.h>
#include <dev/qcom_ess_edma/qcom_ess_edma_gmac.h>
static int
qcom_ess_edma_gmac_mediachange(if_t ifp)
{
struct qcom_ess_edma_gmac *gmac = if_getsoftc(ifp);
struct qcom_ess_edma_softc *sc = gmac->sc;
struct ifmedia *ifm = &gmac->ifm;
struct ifmedia_entry *ife = ifm->ifm_cur;
if (IFM_TYPE(ifm->ifm_media) != IFM_ETHER)
return (EINVAL);
if (IFM_SUBTYPE(ife->ifm_media) == IFM_AUTO) {
device_printf(sc->sc_dev,
"AUTO is not supported this MAC");
return (EINVAL);
}
return (0);
}
static void
qcom_ess_edma_gmac_mediastatus(if_t ifp, struct ifmediareq *ifmr)
{
ifmr->ifm_status = IFM_AVALID | IFM_ACTIVE;
ifmr->ifm_active = IFM_ETHER | IFM_1000_T | IFM_FDX;
}
static int
qcom_ess_edma_gmac_ioctl(if_t ifp, u_long command, caddr_t data)
{
struct qcom_ess_edma_gmac *gmac = if_getsoftc(ifp);
struct qcom_ess_edma_softc *sc = gmac->sc;
struct ifreq *ifr = (struct ifreq *) data;
int error, mask;
switch (command) {
case SIOCSIFFLAGS:
if ((if_getflags(ifp) & IFF_UP) != 0) {
QCOM_ESS_EDMA_DPRINTF(sc, QCOM_ESS_EDMA_DBG_STATE,
"%s: gmac%d: IFF_UP\n",
__func__,
gmac->id);
if_setdrvflagbits(ifp, IFF_DRV_RUNNING,
IFF_DRV_OACTIVE);
if_link_state_change(ifp, LINK_STATE_UP);
} else if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) != 0) {
if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING);
QCOM_ESS_EDMA_DPRINTF(sc, QCOM_ESS_EDMA_DBG_STATE,
"%s: gmac%d: IF down\n",
__func__,
gmac->id);
if_link_state_change(ifp, LINK_STATE_DOWN);
}
error = 0;
break;
case SIOCGIFMEDIA:
case SIOCSIFMEDIA:
error = ifmedia_ioctl(ifp, ifr, &gmac->ifm, command);
break;
case SIOCSIFCAP:
mask = ifr->ifr_reqcap ^ if_getcapenable(ifp);
error = 0;
if ((mask & IFCAP_RXCSUM) != 0 &&
(if_getcapabilities(ifp) & IFCAP_RXCSUM) != 0)
if_togglecapenable(ifp, IFCAP_RXCSUM);
if ((mask & IFCAP_VLAN_HWTAGGING) != 0 &&
(if_getcapabilities(ifp) & IFCAP_VLAN_HWTAGGING) != 0)
if_togglecapenable(ifp, IFCAP_VLAN_HWTAGGING);
VLAN_CAPABILITIES(ifp);
break;
default:
error = ether_ioctl(ifp, command, data);
break;
}
return (error);
}
static void
qcom_ess_edma_gmac_init(void *arg)
{
struct qcom_ess_edma_gmac *gmac = arg;
struct qcom_ess_edma_softc *sc = gmac->sc;
QCOM_ESS_EDMA_DPRINTF(sc, QCOM_ESS_EDMA_DBG_STATE,
"%s: gmac%d: called\n",
__func__,
gmac->id);
if_setdrvflagbits(gmac->ifp, IFF_DRV_RUNNING, IFF_DRV_OACTIVE);
if_link_state_change(gmac->ifp, LINK_STATE_UP);
}
static int
qcom_ess_edma_gmac_transmit(if_t ifp, struct mbuf *m)
{
struct qcom_ess_edma_gmac *gmac = if_getsoftc(ifp);
struct qcom_ess_edma_softc *sc = gmac->sc;
struct qcom_ess_edma_tx_state *txs;
int ret;
int q;
sched_pin();
if (M_HASHTYPE_GET(m) != M_HASHTYPE_NONE) {
q = m->m_pkthdr.flowid % sc->sc_config.num_tx_queue_per_cpu;
q = q << (mp_ncpus * curcpu);
q = q % QCOM_ESS_EDMA_NUM_TX_RINGS;
} else {
q = (mp_ncpus * curcpu) % QCOM_ESS_EDMA_NUM_TX_RINGS;
}
txs = &sc->sc_tx_state[q];
m->m_pkthdr.rcvif = ifp;
ret = buf_ring_enqueue(txs->br, m);
if (ret == 0) {
if (atomic_cmpset_int(&txs->enqueue_is_running, 0, 1) == 1) {
taskqueue_enqueue(txs->completion_tq, &txs->xmit_task);
}
}
sched_unpin();
return (ret);
}
static void
qcom_ess_edma_gmac_qflush(if_t ifp)
{
struct qcom_ess_edma_gmac *gmac = if_getsoftc(ifp);
struct qcom_ess_edma_softc *sc = gmac->sc;
device_printf(sc->sc_dev, "%s: gmac%d: called\n",
__func__,
gmac->id);
}
int
qcom_ess_edma_gmac_parse(struct qcom_ess_edma_softc *sc, int gmac_id)
{
struct qcom_ess_edma_gmac *gmac;
char gmac_name[10];
uint32_t vlan_tag[2];
phandle_t p;
int len;
sprintf(gmac_name, "gmac%d", gmac_id);
gmac = &sc->sc_gmac[gmac_id];
p = ofw_bus_find_child(ofw_bus_get_node(sc->sc_dev), gmac_name);
if (p <= 0) {
device_printf(sc->sc_dev,
"%s: couldn't find %s\n", __func__,
gmac_name);
return (ENOENT);
}
len = OF_getprop(p, "local-mac-address", (void *) &gmac->eaddr,
sizeof(struct ether_addr));
if (len != sizeof(struct ether_addr)) {
device_printf(sc->sc_dev,
"gmac%d: Couldn't parse local-mac-address\n",
gmac_id);
memset(&gmac->eaddr, 0, sizeof(gmac->eaddr));
}
len = OF_getproplen(p, "vlan_tag");
if (len != sizeof(vlan_tag)) {
device_printf(sc->sc_dev,
"gmac%d: no vlan_tag field or invalid size/values\n",
gmac_id);
return (EINVAL);
}
len = OF_getencprop(p, "vlan_tag", (void *) &vlan_tag,
sizeof(vlan_tag));
if (len != sizeof(vlan_tag)) {
device_printf(sc->sc_dev,
"gmac%d: couldn't parse vlan_tag field\n", gmac_id);
return (EINVAL);
}
gmac->sc = sc;
gmac->id = gmac_id;
gmac->enabled = true;
gmac->vlan_id = vlan_tag[0];
gmac->port_mask = vlan_tag[1];
device_printf(sc->sc_dev,
"gmac%d: MAC=%6D, vlan id=%d, port_mask=0x%04x\n",
gmac_id,
&gmac->eaddr, ":",
gmac->vlan_id,
gmac->port_mask);
return (0);
}
int
qcom_ess_edma_gmac_create_ifnet(struct qcom_ess_edma_softc *sc, int gmac_id)
{
struct qcom_ess_edma_gmac *gmac;
char gmac_name[10];
sprintf(gmac_name, "gmac%d", gmac_id);
gmac = &sc->sc_gmac[gmac_id];
if (gmac->enabled == false)
return (0);
gmac->ifp = if_alloc(IFT_ETHER);
if (gmac->ifp == NULL) {
device_printf(sc->sc_dev, "gmac%d: couldn't allocate ifnet\n",
gmac_id);
return (ENOSPC);
}
if_setsoftc(gmac->ifp, gmac);
if_initname(gmac->ifp, "gmac", gmac_id);
if (ETHER_IS_ZERO(gmac->eaddr.octet)) {
device_printf(sc->sc_dev, "gmac%d: generating random MAC\n",
gmac_id);
ether_gen_addr(gmac->ifp, (void *) &gmac->eaddr.octet);
}
if_setflags(gmac->ifp, IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST);
if_setioctlfn(gmac->ifp, qcom_ess_edma_gmac_ioctl);
if_setinitfn(gmac->ifp, qcom_ess_edma_gmac_init);
if_settransmitfn(gmac->ifp, qcom_ess_edma_gmac_transmit);
if_setqflushfn(gmac->ifp, qcom_ess_edma_gmac_qflush);
if_setcapabilitiesbit(gmac->ifp, IFCAP_VLAN_MTU |
IFCAP_VLAN_HWTAGGING, 0);
if_setcapabilitiesbit(gmac->ifp, IFCAP_RXCSUM, 0);
if_clearhwassist(gmac->ifp);
ifmedia_init(&gmac->ifm, 0, qcom_ess_edma_gmac_mediachange,
qcom_ess_edma_gmac_mediastatus);
ifmedia_add(&gmac->ifm, IFM_ETHER | IFM_1000_T | IFM_FDX, 0, NULL);
ifmedia_set(&gmac->ifm, IFM_ETHER | IFM_1000_T | IFM_FDX);
ether_ifattach(gmac->ifp, (char *) &gmac->eaddr);
if_setcapenable(gmac->ifp, if_getcapabilities(gmac->ifp));
return (0);
}
int
qcom_ess_edma_gmac_setup_port_mapping(struct qcom_ess_edma_softc *sc,
int gmac_id)
{
struct qcom_ess_edma_gmac *gmac;
int i;
gmac = &sc->sc_gmac[gmac_id];
if (gmac->enabled == false)
return (0);
for (i = 0; i < QCOM_ESS_EDMA_MAX_NUM_PORTS; i++) {
if ((gmac->port_mask & (1U << i)) == 0)
continue;
if (sc->sc_gmac_port_map[i] != -1) {
device_printf(sc->sc_dev,
"DUPLICATE GMAC port map (port %d)\n",
i);
return (ENXIO);
}
sc->sc_gmac_port_map[i] = gmac_id;
if (bootverbose)
device_printf(sc->sc_dev,
"ESS port %d maps to gmac%d\n",
i, gmac_id);
}
return (0);
}
int
qcom_ess_edma_gmac_receive_frames(struct qcom_ess_edma_softc *sc,
int rx_queue, struct mbufq *mq)
{
struct qcom_ess_edma_desc_ring *ring;
struct epoch_tracker et;
struct mbuf *m;
if_t ifp;
ring = &sc->sc_rx_ring[rx_queue];
NET_EPOCH_ENTER(et);
while ((m = mbufq_dequeue(mq)) != NULL) {
if (m->m_pkthdr.rcvif == NULL) {
ring->stats.num_rx_no_gmac++;
m_freem(m);
} else {
ring->stats.num_rx_ok++;
ifp = m->m_pkthdr.rcvif;
if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1);
if_input(ifp, m);
}
}
NET_EPOCH_EXIT(et);
return (0);
}