#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: if_qn.c,v 1.53 2022/09/17 19:23:24 thorpej Exp $");
#include "qn.h"
#if NQN > 0
#define QN_DEBUG
#define QN_DEBUG1_no
#define QN_CHECKS_no
#include "opt_inet.h"
#include "opt_ns.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/buf.h>
#include <sys/device.h>
#include <sys/protosw.h>
#include <sys/socket.h>
#include <sys/syslog.h>
#include <sys/ioctl.h>
#include <sys/errno.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_ether.h>
#include <net/bpf.h>
#ifdef INET
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/in_var.h>
#include <netinet/ip.h>
#include <netinet/if_inarp.h>
#endif
#include <machine/cpu.h>
#include <amiga/amiga/device.h>
#include <amiga/amiga/isr.h>
#include <amiga/dev/zbusvar.h>
#include <amiga/dev/if_qnreg.h>
#define NIC_R_MASK (R_INT_PKT_RDY | R_INT_ALG_ERR |\
R_INT_CRC_ERR | R_INT_OVR_FLO)
#define MAX_PACKETS 30
struct qn_softc {
device_t sc_dev;
struct isr sc_isr;
struct ethercom sc_ethercom;
u_char volatile *sc_base;
u_char volatile *sc_nic_base;
u_short volatile *nic_fifo;
u_short volatile *nic_r_status;
u_short volatile *nic_t_status;
u_short volatile *nic_r_mask;
u_short volatile *nic_t_mask;
u_short volatile *nic_r_mode;
u_short volatile *nic_t_mode;
u_short volatile *nic_reset;
u_short volatile *nic_len;
bool transmit_pending;
};
int qnmatch(device_t, cfdata_t, void *);
void qnattach(device_t, device_t, void *);
int qnintr(void *);
int qnioctl(struct ifnet *, u_long, void *);
void qnstart(struct ifnet *);
void qnwatchdog(struct ifnet *);
void qnreset(struct qn_softc *);
void qninit(struct qn_softc *);
void qnstop(struct qn_softc *);
static u_short qn_put(u_short volatile *, struct mbuf *);
static void qn_rint(struct qn_softc *, u_short);
static void qn_flush(struct qn_softc *);
static void inline word_copy_from_card(u_short volatile *, u_short *, u_short);
static void inline word_copy_to_card(u_short *, u_short volatile *,
register u_short);
static void qn_get_packet(struct qn_softc *, u_short);
#ifdef QN_DEBUG1
static void qn_dump(struct qn_softc *);
#endif
CFATTACH_DECL_NEW(qn, sizeof(struct qn_softc),
qnmatch, qnattach, NULL, NULL);
int
qnmatch(device_t parent, cfdata_t cf, void *aux)
{
struct zbus_args *zap;
zap = (struct zbus_args *)aux;
if (zap->manid == 2011 && zap->prodid == 2)
return (1);
return (0);
}
void
qnattach(device_t parent, device_t self, void *aux)
{
struct zbus_args *zap;
struct qn_softc *sc = device_private(self);
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
u_int8_t myaddr[ETHER_ADDR_LEN];
sc->sc_dev = self;
zap = (struct zbus_args *)aux;
sc->sc_base = zap->va;
sc->sc_nic_base = sc->sc_base + QUICKNET_NIC_BASE;
sc->nic_fifo = (u_short volatile *)(sc->sc_nic_base + NIC_BMPR0);
sc->nic_len = (u_short volatile *)(sc->sc_nic_base + NIC_BMPR2);
sc->nic_t_status = (u_short volatile *)(sc->sc_nic_base + NIC_DLCR0);
sc->nic_r_status = (u_short volatile *)(sc->sc_nic_base + NIC_DLCR2);
sc->nic_t_mask = (u_short volatile *)(sc->sc_nic_base + NIC_DLCR1);
sc->nic_r_mask = (u_short volatile *)(sc->sc_nic_base + NIC_DLCR3);
sc->nic_t_mode = (u_short volatile *)(sc->sc_nic_base + NIC_DLCR4);
sc->nic_r_mode = (u_short volatile *)(sc->sc_nic_base + NIC_DLCR5);
sc->nic_reset = (u_short volatile *)(sc->sc_nic_base + NIC_DLCR6);
sc->transmit_pending = false;
myaddr[0] = 0x5c;
myaddr[1] = 0x5c;
myaddr[2] = 0x00;
myaddr[3] = (zap->serno >> 16) & 0xff;
myaddr[4] = (zap->serno >> 8) & 0xff;
myaddr[5] = zap->serno & 0xff;
qnstop(sc);
memcpy(ifp->if_xname, device_xname(sc->sc_dev), IFNAMSIZ);
ifp->if_softc = sc;
ifp->if_ioctl = qnioctl;
ifp->if_watchdog = qnwatchdog;
ifp->if_start = qnstart;
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX;
ifp->if_mtu = ETHERMTU;
if_attach(ifp);
if_deferred_start_init(ifp, NULL);
ether_ifattach(ifp, myaddr);
#ifdef QN_DEBUG
printf(": hardware address %s\n", ether_sprintf(myaddr));
#endif
sc->sc_isr.isr_intr = qnintr;
sc->sc_isr.isr_arg = sc;
sc->sc_isr.isr_ipl = 2;
add_isr(&sc->sc_isr);
}
void
qninit(struct qn_softc *sc)
{
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
u_short i;
static int retry = 0;
*sc->nic_r_mask = NIC_R_MASK;
*sc->nic_t_mode = NO_LOOPBACK;
if (sc->sc_ethercom.ec_if.if_flags & IFF_PROMISC) {
*sc->nic_r_mode = PROMISCUOUS_MODE;
log(LOG_INFO, "qn: Promiscuous mode (not tested)\n");
} else
*sc->nic_r_mode = NORMAL_MODE;
for (i = 0; i < ETHER_ADDR_LEN; i++)
*((u_short volatile *)(sc->sc_nic_base+
QNET_HARDWARE_ADDRESS+2*i)) =
((((u_short)CLLADDR(ifp->if_sadl)[i]) << 8) |
CLLADDR(ifp->if_sadl)[i]);
ifp->if_flags |= IFF_RUNNING;
sc->transmit_pending = false;
qn_flush(sc);
if (retry == 0) {
*((u_short volatile *)(sc->sc_nic_base + QNET_MAGIC)) = 0;
retry = 1;
}
*sc->nic_reset = ENABLE_DLC;
if_schedule_deferred_start(ifp);
}
void
qnwatchdog(struct ifnet *ifp)
{
struct qn_softc *sc = ifp->if_softc;
log(LOG_INFO, "qn: device timeout (watchdog)\n");
if_statinc(ifp, if_oerrors);
qnreset(sc);
}
static void
qn_flush(struct qn_softc *sc)
{
#if 1
while (!(*sc->nic_r_status & R_BUS_RD_ERR))
(void)(*sc->nic_fifo);
#else
(void)(*sc->nic_fifo);
(void)(*sc->nic_fifo);
#endif
*sc->nic_r_status = R_BUS_RD_ERR;
}
void
qnreset(struct qn_softc *sc)
{
int s;
s = splnet();
qnstop(sc);
qninit(sc);
splx(s);
}
void
qnstop(struct qn_softc *sc)
{
*sc->nic_reset = DISABLE_DLC;
delay(200);
*sc->nic_t_status = CLEAR_T_ERR;
*sc->nic_t_mask = CLEAR_T_MASK;
*sc->nic_r_status = CLEAR_R_ERR;
*sc->nic_r_mask = CLEAR_R_MASK;
*((u_short volatile *)(sc->sc_nic_base + NIC_BMPR4)) = 0;
*sc->nic_r_mode = 0;
*sc->nic_t_mode = 0;
qn_flush(sc);
}
void
qnstart(struct ifnet *ifp)
{
struct qn_softc *sc = ifp->if_softc;
struct mbuf *m;
u_short len;
int timout = 60000;
if ((ifp->if_flags & IFF_RUNNING) == 0)
return;
if (sc->transmit_pending)
return;
IF_DEQUEUE(&ifp->if_snd, m);
if (m == 0)
return;
bpf_mtap(ifp, m, BPF_D_OUT);
len = qn_put(sc->nic_fifo, m);
m_freem(m);
len = ((len >> 8) & 0x0007) | TRANSMIT_START | ((len & 0x00ff) << 8);
*sc->nic_len = len;
while (!(*sc->nic_t_status & T_TMT_OK) && --timout) {
if ((timout % 10000) == 0)
log(LOG_INFO, "qn: timeout...\n");
}
if (timout == 0)
log(LOG_INFO, "qn: transmit timeout (fatal?)\n");
sc->transmit_pending = true;
*sc->nic_t_mask = INT_TMT_OK | INT_SIXTEEN_COL;
ifp->if_timer = 2;
}
static void inline
word_copy_from_card(u_short volatile *card, u_short *b, u_short len)
{
register u_short l = len/2;
while (l--)
*b++ = *card;
}
static void inline
word_copy_to_card(u_short *a, u_short volatile *card, register u_short len)
{
while (len--)
*card = *a++;
}
static u_short
qn_put(u_short volatile *addr, struct mbuf *m)
{
u_short *data;
u_char savebyte[2];
int len, len1, wantbyte;
u_short totlen;
totlen = wantbyte = 0;
for (; m != NULL; m = m->m_next) {
data = mtod(m, u_short *);
len = m->m_len;
if (len > 0) {
totlen += len;
if (wantbyte) {
savebyte[1] = *((u_char *)data);
*addr = *((u_short *)savebyte);
data = (u_short *)((u_char *)data + 1);
len--;
wantbyte = 0;
}
if (len > 1) {
len1 = len/2;
word_copy_to_card(data, addr, len1);
data += len1;
len &= 1;
}
if (len == 1) {
savebyte[0] = *((u_char *)data);
wantbyte = 1;
}
}
}
if (wantbyte) {
savebyte[1] = 0;
*addr = *((u_short *)savebyte);
}
if(totlen < (ETHER_MIN_LEN - ETHER_CRC_LEN)) {
for(len = totlen + 1; len < (ETHER_MIN_LEN - ETHER_CRC_LEN);
len += 2)
*addr = (u_short)0x0000;
totlen = (ETHER_MIN_LEN - ETHER_CRC_LEN);
}
return (totlen);
}
static void
qn_get_packet(struct qn_softc *sc, u_short len)
{
register u_short volatile *nic_fifo_ptr = sc->nic_fifo;
struct ifnet *ifp = &sc->sc_ethercom.ec_if;
struct mbuf *m, *dst, *head = NULL;
register u_short len1;
u_short amount;
MGETHDR(m, M_DONTWAIT, MT_DATA);
if (m == NULL)
goto bad;
if (len & 1)
len++;
m_set_rcvif(m, &sc->sc_ethercom.ec_if);
m->m_pkthdr.len = len;
m->m_len = 0;
head = m;
word_copy_from_card(nic_fifo_ptr,
mtod(head, u_short *),
sizeof(struct ether_header));
head->m_len += sizeof(struct ether_header);
len -= sizeof(struct ether_header);
while (len > 0) {
len1 = len;
amount = M_TRAILINGSPACE(m);
if (amount == 0) {
dst = m;
MGET(m, M_DONTWAIT, MT_DATA);
if (m == NULL)
goto bad;
if (len1 >= MINCLSIZE)
MCLGET(m, M_DONTWAIT);
m->m_len = 0;
dst->m_next = m;
amount = M_TRAILINGSPACE(m);
}
if (amount < len1)
len1 = amount;
word_copy_from_card(nic_fifo_ptr,
(u_short *)(mtod(m, char *) + m->m_len),
len1);
m->m_len += len1;
len -= len1;
}
if_percpuq_enqueue(ifp->if_percpuq, head);
return;
bad:
if (head) {
m_freem(head);
log(LOG_INFO, "qn_get_packet: mbuf alloc failed\n");
}
}
static void
qn_rint(struct qn_softc *sc, u_short rstat)
{
int i;
u_short len, status;
*sc->nic_r_status = CLEAR_R_ERR;
if (rstat & R_INT_OVR_FLO) {
#ifdef QN_DEBUG
log(LOG_INFO, "Overflow\n");
#endif
if_statinc(&sc->sc_ethercom.ec_if, if_ierrors);
}
if (rstat & R_INT_CRC_ERR) {
#ifdef QN_DEBUG
log(LOG_INFO, "CRC Error\n");
#endif
if_statinc(&sc->sc_ethercom.ec_if, if_ierrors);
}
if (rstat & R_INT_ALG_ERR) {
#ifdef QN_DEBUG
log(LOG_INFO, "Alignment error\n");
#endif
if_statinc(&sc->sc_ethercom.ec_if, if_ierrors);
}
if (rstat & R_INT_SRT_PKT) {
#ifdef QN_DEBUG
log(LOG_INFO, "Short packet\n");
#endif
if_statinc(&sc->sc_ethercom.ec_if, if_ierrors);
}
if (rstat & 0x4040) {
#ifdef QN_DEBUG
log(LOG_INFO, "Bus read error\n");
#endif
if_statinc(&sc->sc_ethercom.ec_if, if_ierrors);
qnreset(sc);
}
for (i = 0; i < MAX_PACKETS; i++) {
if (*sc->nic_r_mode & RM_BUF_EMP)
break;
status = *sc->nic_fifo;
if ((status & 0x7000) != 0x2000) {
log(LOG_INFO, "qn: ERROR: status=%04x\n", status);
continue;
}
len = *sc->nic_fifo;
len = ((len << 8) & 0xff00) | ((len >> 8) & 0x00ff);
#ifdef QN_CHECKS
if (len > (ETHER_MAX_LEN - ETHER_CRC_LEN) ||
len < ETHER_HDR_LEN) {
log(LOG_WARNING,
"%s: received a %s packet? (%u bytes)\n",
device_xname(sc->sc_dev),
len < ETHER_HDR_LEN ? "partial" : "big", len);
if_statinc(&sc->sc_ethercom.ec_if, if_ierrors);
continue;
}
#endif
#ifdef QN_CHECKS
if (len < (ETHER_MIN_LEN - ETHER_CRC_LEN))
log(LOG_WARNING,
"%s: received a short packet? (%u bytes)\n",
device_xname(sc->sc_dev), len);
#endif
qn_get_packet(sc, len);
}
#ifdef QN_DEBUG
if (i == MAX_PACKETS)
log(LOG_INFO, "used all the %d loops\n", MAX_PACKETS);
#endif
}
int
qnintr(void *arg)
{
struct qn_softc *sc = arg;
u_short tint, rint, tintmask;
char return_tintmask = 0;
if (sc->sc_base == NULL)
return (0);
rint = (*sc->nic_r_status) & NIC_R_MASK;
tintmask = *sc->nic_t_mask;
tint = (*sc->nic_t_status) & tintmask;
if (tint == 0 && rint == 0)
return (0);
*sc->nic_r_mask = CLEAR_R_MASK;
*sc->nic_t_mask = CLEAR_T_MASK;
if (tint != 0) {
*sc->nic_t_status = CLEAR_T_ERR;
if (sc->transmit_pending && (tint & T_TMT_OK)) {
sc->transmit_pending = false;
if_statinc(&sc->sc_ethercom.ec_if, if_opackets);
}
if (tint & T_SIXTEEN_COL) {
log(LOG_INFO, "qn: 16 collision - packet lost\n");
#ifdef QN_DEBUG1
qn_dump(sc);
#endif
if_statadd2(&sc->sc_ethercom.ec_if,
if_oerrors, 1, if_collisions, 16);
sc->transmit_pending = false;
}
if (sc->transmit_pending) {
log(LOG_INFO, "qn:still pending...\n");
return_tintmask = 1;
} else {
sc->sc_ethercom.ec_if.if_timer = 0;
}
} else
return_tintmask = 1;
if (rint != 0)
qn_rint(sc, rint);
if (!sc->transmit_pending)
if_schedule_deferred_start(&sc->sc_ethercom.ec_if);
else if (return_tintmask == 1)
*sc->nic_t_mask = tintmask;
*sc->nic_r_mask = NIC_R_MASK;
return (1);
}
int
qnioctl(register struct ifnet *ifp, u_long cmd, void *data)
{
struct qn_softc *sc = ifp->if_softc;
register struct ifaddr *ifa = (struct ifaddr *)data;
#if 0
struct ifreg *ifr = (struct ifreg *)data;
#endif
int s, error = 0;
s = splnet();
switch (cmd) {
case SIOCINITIFADDR:
ifp->if_flags |= IFF_UP;
switch (ifa->ifa_addr->sa_family) {
#ifdef INET
case AF_INET:
qnstop(sc);
qninit(sc);
arp_ifinit(ifp, ifa);
break;
#endif
default:
log(LOG_INFO, "qn:sa_family:default (not tested)\n");
qnstop(sc);
qninit(sc);
break;
}
break;
case SIOCSIFFLAGS:
if ((error = ifioctl_common(ifp, cmd, data)) != 0)
break;
if ((ifp->if_flags & IFF_UP) == 0 &&
(ifp->if_flags & IFF_RUNNING) != 0) {
#ifdef QN_DEBUG1
qn_dump(sc);
#endif
qnstop(sc);
ifp->if_flags &= ~IFF_RUNNING;
} else if ((ifp->if_flags & IFF_UP) != 0 &&
(ifp->if_flags & IFF_RUNNING) == 0) {
qninit(sc);
} else {
#ifdef QN_DEBUG1
log(LOG_INFO, "Else branch...\n");
#endif
}
break;
case SIOCADDMULTI:
case SIOCDELMULTI:
log(LOG_INFO, "qnioctl: multicast not done yet\n");
#if 0
if ((error = ether_ioctl(ifp, cmd, data)) == ENETRESET) {
log(LOG_INFO, "qnioctl: multicast not done yet\n");
error = 0;
}
#else
error = EINVAL;
#endif
break;
default:
error = ether_ioctl(ifp, cmd, data);
}
splx(s);
return (error);
}
#ifdef QN_DEBUG1
static void
qn_dump(struct qn_softc *sc)
{
log(LOG_INFO, "t_status : %04x\n", *sc->nic_t_status);
log(LOG_INFO, "t_mask : %04x\n", *sc->nic_t_mask);
log(LOG_INFO, "t_mode : %04x\n", *sc->nic_t_mode);
log(LOG_INFO, "r_status : %04x\n", *sc->nic_r_status);
log(LOG_INFO, "r_mask : %04x\n", *sc->nic_r_mask);
log(LOG_INFO, "r_mode : %04x\n", *sc->nic_r_mode);
log(LOG_INFO, "pending : %s\n", sc->transmit_pending ? "T" : "F");
log(LOG_INFO, "if_flags : %04x\n", sc->sc_ethercom.ec_if.if_flags);
}
#endif
#endif