#include "etherpci_private.h"
#include <ether_driver.h>
#include <OS.h>
#include <KernelExport.h>
#include <Drivers.h>
#include <PCI.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#define kDevName "etherpci"
#define kDevDir "net/" kDevName "/"
#define DEVNAME_LENGTH 64
#define MAX_CARDS 4
int32 api_version = B_CUR_DRIVER_API_VERSION;
#define ERR 0x0001
#define INFO 0x0002
#define RX 0x0004
#define TX 0x0008
#define INTERRUPT 0x0010
#define FUNCTION 0x0020
#define PCI_IO 0x0040
#define SEQ 0x0080
#define WARN 0x0100
#define DEFAULT_DEBUG_FLAGS ( ERR | INFO | WARN | FUNCTION )
#ifdef TRACE_ETHERPCI
# define ETHER_DEBUG(mask, enabled, format, args...) \
do { if (mask & enabled) \
dprintf(format , ##args); } while (0)
#else
# define ETHER_DEBUG(mask, enabled, format, args...) ;
#endif
static pci_module_info *gPCIModInfo;
static char *gDevNameList[MAX_CARDS+1];
static pci_info *gDevList[MAX_CARDS+1];
static int32 gOpenMask = 0;
status_t init_hardware(void);
status_t init_driver(void);
void uninit_driver(void);
const char** publish_devices(void);
device_hooks *find_device(const char *name);
#define STAY_ON_PAGE_0 0
#define INTS_WE_CARE_ABOUT (ISR_RECEIVE | ISR_RECEIVE_ERROR |\
ISR_TRANSMIT | ISR_TRANSMIT_ERROR | ISR_COUNTER)
typedef struct etherpci_private {
int32 devID;
pci_info *pciInfo;
uint16 irq;
uint32 reg_base;
area_id ioarea;
int boundary;
ether_address_t myaddr;
unsigned nmulti;
ether_address_t multi[MAX_MULTI];
sem_id iolock;
int nonblocking;
#if !STAY_ON_PAGE_0
spinlock intrlock;
#endif
volatile int interrupted;
sem_id inrw;
sem_id ilock;
sem_id olock;
volatile int ints;
volatile int rints;
volatile int wints;
volatile int reads;
volatile int writes;
volatile int resets;
volatile int rerrs;
volatile int werrs;
volatile int interrs;
volatile int frame_errs;
volatile int crc_errs;
volatile int frames_lost;
int rerrs_last;
int werrs_last;
int interrs_last;
int frame_errs_last;
int crc_errs_last;
int frames_lost_last;
int chip_rx_frame_errors;
int chip_rx_crc_errors;
int chip_rx_missed_errors;
int ETHER_BUF_START;
int ETHER_BUF_SIZE;
int EC_VMEM_PAGE;
int EC_VMEM_NPAGES;
int EC_RXBUF_START_PAGE;
int EC_RXBUF_END_PAGE;
int EC_RINGSTART;
int EC_RINGSIZE;
uint32 debug;
} etherpci_private_t;
#if __POWERPC__
#define ether_inb(device, offset) (*((volatile uint8*)(device->reg_base + (offset)))); __eieio()
#define ether_inw(device, offset) (*((volatile uint16*)(device->reg_base + (offset)))); __eieio()
#define ether_outb(device, offset, value) (*((volatile uint8 *)(device->reg_base + (offset))) = (value)); __eieio()
#define ether_outw(device, offset, value) (*((volatile uint16*)(device->reg_base + (offset))) = (value)); __eieio()
#else
#define ether_outb(device, offset, value) (*gPCIModInfo->write_io_8)((device->reg_base + (offset)), (value))
#define ether_outw(device, offset, value) (*gPCIModInfo->write_io_16)((device->reg_base + (offset)), (value))
#define ether_inb(device, offset) ((*gPCIModInfo->read_io_8)(device->reg_base + (offset)))
#define ether_inw(device, offset) ((*gPCIModInfo->read_io_16)(device->reg_base + (offset)))
#endif
#if 0
#if __i386__
uint8 ether_inb(etherpci_private_t *device, uint32 offset) {
uint8 result;
result = ((*gPCIModInfo->read_io_8)(device->reg_base + (offset)));
ETHER_DEBUG(PCI_IO, device->debug, " inb(%x) %x \n", offset, result);
return result;
};
uint16 ether_inw(etherpci_private_t *device, uint32 offset) {
uint16 result;
result = ((*gPCIModInfo->read_io_16)(device->reg_base + (offset)));
ETHER_DEBUG(PCI_IO, device->debug, " inw(%x) %x \n", offset, result);
return result;
};
void ether_outb(etherpci_private_t *device, uint32 offset, uint8 value) {
(*gPCIModInfo->write_io_8)((device->reg_base + (offset)), (value));
ETHER_DEBUG(PCI_IO, device->debug, " outb(%x) %x \n", offset, value);
};
void ether_outw(etherpci_private_t *device, uint32 offset, uint16 value) {
(*gPCIModInfo->write_io_16)((device->reg_base + (offset)), (value));
ETHER_DEBUG(PCI_IO, device->debug, " outb(%x) %x \n", offset, value);
};
#else
uint8 ether_inb(etherpci_private_t *device, uint32 offset) {
uint8 result;
result = (*((volatile uint8*) (device->reg_base + (offset)))); __eieio();
ETHER_DEBUG(PCI_IO, device->debug, " inb(%x) %x \n", offset, result);
return result;
};
uint16 ether_inw(etherpci_private_t *device, uint32 offset) {
uint16 result;
result = (*((volatile uint16*) (device->reg_base + (offset)))); __eieio();
ETHER_DEBUG(PCI_IO, device->debug, " inw(%x) %x \n", offset, result);
return result;
};
void ether_outb(etherpci_private_t *device, uint32 offset, uint8 value) {
(*((volatile uint8 *)(device->reg_base + (offset))) = (value)); __eieio();
ETHER_DEBUG(PCI_IO, device->debug, " outb(%x) %x \n", offset, value);
};
void ether_outw(etherpci_private_t *device, uint32 offset, uint16 value) {
(*((volatile uint16 *)(device->reg_base + (offset))) = (value)); __eieio();
ETHER_DEBUG(PCI_IO, device->debug, " outb(%x) %x \n", offset, value);
};
#endif
#endif
#define DEBUGGER_COMMAND true
#if DEBUGGER_COMMAND
etherpci_private_t * gdev;
static int etherpci(int argc, char **argv);
#endif
#define io_lock(data) acquire_sem(data->iolock)
#define io_unlock(data) release_sem_etc(data->iolock, 1, B_DO_NOT_RESCHEDULE)
#define output_wait(data, t) acquire_sem_etc(data->olock, 1, B_TIMEOUT, t)
#define output_unwait(data, c) release_sem_etc(data->olock, c, B_DO_NOT_RESCHEDULE)
#define input_wait(data) acquire_sem_etc(data->ilock ,1, B_CAN_INTERRUPT, 0)
#define input_unwait(data, c) release_sem_etc(data->ilock, c, B_DO_NOT_RESCHEDULE)
static status_t open_hook(const char *name, uint32 flags, void **cookie);
static status_t close_hook(void *);
static status_t free_hook(void *);
static status_t control_hook(void * cookie,uint32 msg,void *buf,size_t len);
static status_t read_hook(void *data, off_t pos, void *buf, size_t *len);
static status_t write_hook(void *data, off_t pos, const void *buf, size_t *len);
static device_hooks gDeviceHooks = {
open_hook,
close_hook,
free_hook,
control_hook,
read_hook,
write_hook,
NULL,
NULL,
NULL,
NULL
};
static int32
get_pci_list(pci_info *info[], int32 maxEntries)
{
int32 i, entries;
pci_info *item;
item = (pci_info *)malloc(sizeof(pci_info));
if (item == NULL)
return 0;
for (i = 0, entries = 0; entries < maxEntries; i++) {
if (gPCIModInfo->get_nth_pci_info(i, item) != B_OK)
break;
if ((item->vendor_id == 0x10ec && item->device_id == 0x8029)
|| (item->vendor_id == 0x1106 && item->device_id == 0x0926)
|| (item->vendor_id == 0x4a14 && item->device_id == 0x5000)
|| (item->vendor_id == 0x1050 && item->device_id == 0x0940)
|| (item->vendor_id == 0x11f6 && item->device_id == 0x1401)
|| (item->vendor_id == 0x8e2e && item->device_id == 0x3000)) {
if (item->u.h0.interrupt_line == 0 || item->u.h0.interrupt_line == 0xFF) {
dprintf(kDevName " found with invalid IRQ - check IRQ assignement");
continue;
}
dprintf(kDevName " found at IRQ %x ", item->u.h0.interrupt_line);
info[entries++] = item;
item = (pci_info *)malloc(sizeof(pci_info));
if (item == NULL)
break;
}
}
info[entries] = NULL;
free(item);
return entries;
}
static status_t
free_pci_list(pci_info *info[])
{
pci_info *item;
int32 i;
for (i = 0; (item = info[i]) != NULL; i++) {
free(item);
}
return B_OK;
}
#if 0
static long
io_count(etherpci_private_t *data)
{
long count;
get_sem_count(data->iolock, &count);
return (count);
}
static long
output_count(etherpci_private_t *data)
{
long count;
get_sem_count(data->olock, &count);
return (count);
}
#endif
static int32
input_count(etherpci_private_t *data)
{
int32 count;
get_sem_count(data->ilock, &count);
return (count);
}
#if STAY_ON_PAGE_0
#define INTR_LOCK(data, expression) (expression)
#else
#define intr_lock(data) acquire_spinlock(&data->intrlock)
#define intr_unlock(data) release_spinlock(&data->intrlock)
#define INTR_LOCK(data, expression) (intr_lock(data), (expression), intr_unlock(data))
#endif
static void
calc_constants(etherpci_private_t *data)
{
data->EC_VMEM_PAGE = (data->ETHER_BUF_START >> EC_PAGE_SHIFT);
data->EC_VMEM_NPAGES = (data->ETHER_BUF_SIZE >> EC_PAGE_SHIFT);
data->EC_RXBUF_START_PAGE = (data->EC_VMEM_PAGE + 6);
data->EC_RXBUF_END_PAGE = (data->EC_VMEM_PAGE + data->EC_VMEM_NPAGES);
data->EC_RINGSTART = (data->EC_RXBUF_START_PAGE << EC_PAGE_SHIFT);
data->EC_RINGSIZE = ((data->EC_VMEM_NPAGES - 6) << EC_PAGE_SHIFT);
}
static void
print_address(ether_address_t *addr)
{
int i;
char buf[3 * 6 + 1];
for (i = 0; i < 5; i++) {
sprintf(&buf[3*i], "%02x:", addr->ebyte[i]);
}
sprintf(&buf[3*5], "%02x", addr->ebyte[5]);
dprintf("%s\n", buf);
}
static unsigned char
getisr(etherpci_private_t *data)
{
return ether_inb(data, EN0_ISR);
}
static void
setisr(etherpci_private_t *data, unsigned char isr)
{
ether_outb(data, EN0_ISR, isr);
}
static int
wait_for_dma_complete(etherpci_private_t *data, unsigned short addr,
unsigned short size)
{
unsigned short hi, low;
unsigned short where;
int bogus;
#define MAXBOGUS 20
bogus = 0;
while (!(getisr(data) & ISR_DMADONE) && ++bogus < MAXBOGUS) {
}
if (bogus >= MAXBOGUS)
dprintf("Bogus alert: waiting for ISR\n");
bogus = 0;
do {
hi = ether_inb(data, EN0_RADDRHI);
low = ether_inb(data, EN0_RADDRLO);
where = (hi << 8) | low;
} while (where < addr + size && ++bogus < MAXBOGUS);
if (bogus >= MAXBOGUS * 2) {
dprintf("Bogus alert: waiting for counters to zero\n");
return -1;
}
setisr(data, ISR_DMADONE);
ether_outb(data, EN_CCMD, ENC_NODMA);
return 0;
}
static void
check_transmit_status(etherpci_private_t *data)
{
unsigned char status;
status = ether_inb(data, EN0_TPSR);
if (status & (TSR_ABORTED | TSR_UNDERRUN)) {
dprintf("transmit error: %02x\n", status);
}
#if 0
if (data->wints + data->werrs != data->writes) {
dprintf("Write interrupts %d, errors %d, transmits %d\n",
data->wints, data->werrs, data->writes);
}
#endif
}
static long
inrw(etherpci_private_t *data)
{
return data->inrw;
}
static status_t
create_sems(etherpci_private_t *data)
{
data->iolock = create_sem(1, "ethercard io");
if (data->iolock < B_OK)
return data->iolock;
data->olock = create_sem(1, "ethercard output");
if (data->olock < B_OK) {
delete_sem(data->iolock);
return data->olock;
}
data->ilock = create_sem(0, "ethercard input");
if (data->ilock < B_OK) {
delete_sem(data->iolock);
delete_sem(data->olock);
return data->ilock;
}
return B_OK;
}
static void
etherpci_min(etherpci_private_t *data, unsigned char *dst,
unsigned src, unsigned len)
{
unsigned int i;
if (len & 1)
len++;
ether_outb(data, EN0_RCNTLO, len & 0xff);
ether_outb(data, EN0_RCNTHI, len >> 8);
ether_outb(data, EN0_RADDRLO, src & 0xff);
ether_outb(data, EN0_RADDRHI, src >> 8);
ether_outb(data, EN_CCMD, ENC_DMAREAD);
for (i = 0; i < len; i += 2) {
unsigned short word;
word = ether_inw(data, NE_DATA);
#if __i386__
dst[i + 1] = word >> 8;
dst[i + 0] = word & 0xff;
#else
dst[i] = word >> 8;
dst[i + 1] = word & 0xff;
#endif
}
wait_for_dma_complete(data, src, len);
}
static void
etherpci_mout(etherpci_private_t *data, unsigned dst,
const unsigned char *src, unsigned len)
{
unsigned int i;
int tries = 1;
again:
#define MAXTRIES 2
if (tries > MAXTRIES) {
dprintf("ether_mout : tried %d times, stopping\n", tries);
return;
}
if (len & 1)
len++;
ether_outb(data, EN0_RCNTHI, len >> 8);
ether_outb(data, EN0_RADDRLO, dst & 0xff);
ether_outb(data, EN0_RADDRHI, dst >> 8);
if ((len & 0xff) == 0) {
ether_outb(data, EN0_RCNTLO, 2);
} else {
ether_outb(data, EN0_RCNTLO, len & 0xff);
}
#if you_want_to_follow_step2_even_though_it_hangs
ether_outb(data, EN_CCMD, ENC_DMAREAD);
#endif
ether_outb(data, EN_CCMD, ENC_DMAWRITE);
for (i = 0; i < len; i += 2) {
unsigned short word;
#if __i386__
word = (src[i + 1] << 8) | src[i + 0];
#else
word = (src[i] << 8) | src[i + 1];
#endif
ether_outw(data, NE_DATA, word);
}
if ((len & 0xff) == 0) {
ether_outw(data, NE_DATA, 0);
len += 2;
}
if (wait_for_dma_complete(data, dst, len) != 0) {
tries++;
goto again;
}
}
#if STAY_ON_PAGE_0
static void
ringzero(etherpci_private_t *data, unsigned boundary,
unsigned next_boundary)
{
ring_header ring;
int i;
int pages;
unsigned offset;
ring.count = 0;
ring.next_packet = 0;
ring.status = 0;
if (data->boundary < next_boundary) {
pages = next_boundary - data->boundary;
} else {
pages = (data->EC_RINGSIZE >> EC_PAGE_SHIFT) - (data->boundary - next_boundary);
}
for (i = 0; i < pages; i++) {
offset = data->boundary << EC_PAGE_SHIFT;
etherpci_mout(data, offset, (unsigned char *)&ring, sizeof(ring));
data->boundary++;
if (data->boundary >= data->EC_RXBUF_END_PAGE) {
data->boundary = data->EC_RXBUF_START_PAGE;
}
}
}
#endif
static int
probe(etherpci_private_t *data)
{
unsigned int i;
int reg;
unsigned char test[EC_PAGE_SIZE];
short waddr[ETHER_ADDR_LEN];
uint8 reg_val;
data->ETHER_BUF_START = ETHER_BUF_START_NE2000;
data->ETHER_BUF_SIZE = ETHER_BUF_SIZE_NE2000;
calc_constants(data);
reg = ether_inb(data, NE_RESET);
snooze(2000);
ether_outb(data, NE_RESET, reg);
snooze(2000);
ether_outb(data, EN_CCMD, ENC_NODMA | ENC_STOP | ENC_PAGE0);
i = 10000;
while ( i-- > 0) {
reg_val = ether_inb(data, EN0_ISR);
if (reg_val & ISR_RESET) break;
}
if (i < 0)
dprintf("reset failed -- ignoring\n");
ether_outb(data, EN0_ISR, 0xff);
ether_outb(data, EN0_DCFG, DCFG_BM16);
ether_outb(data, EN_CCMD, ENC_NODMA | ENC_STOP | ENC_PAGE0);
snooze(2000);
reg = ether_inb(data, EN_CCMD);
if (reg != (ENC_NODMA|ENC_STOP|ENC_PAGE0)) {
dprintf("command register failed: %02x != %02x\n", reg, ENC_NODMA|ENC_STOP);
return 0;
}
ether_outb(data, EN0_TXCR, 0);
ether_outb(data, EN0_RXCR, ENRXCR_MON);
ether_outb(data, EN0_STARTPG, data->EC_RXBUF_START_PAGE);
ether_outb(data, EN0_STOPPG, data->EC_RXBUF_END_PAGE);
ether_outb(data, EN0_BOUNDARY, data->EC_RXBUF_END_PAGE);
ether_outb(data, EN0_IMR, 0);
ether_outb(data, EN0_ISR, 0);
ether_outb(data, EN_CCMD, ENC_NODMA | ENC_PAGE1 | ENC_STOP);
ether_outb(data, EN1_CURPAG, data->EC_RXBUF_START_PAGE);
ether_outb(data, EN_CCMD, ENC_NODMA | ENC_PAGE0 | ENC_STOP);
ether_outb(data, EN_CCMD, ENC_NODMA | ENC_PAGE0);
ether_outb(data, EN_CCMD, ENC_NODMA | ENC_STOP);
memset(&waddr[0], 0, sizeof(waddr));
etherpci_min(data, (unsigned char *)&waddr[0], 0, sizeof(waddr));
for (i = 0; i < ETHER_ADDR_LEN; i++) {
data->myaddr.ebyte[i] = ((unsigned char *)&waddr[0])[2*i];
}
for (i = 0; i < sizeof(test); i++) {
test[i] = i;
}
etherpci_mout(data, data->ETHER_BUF_START, (unsigned char *)&test[0], sizeof(test));
memset(&test, 0, sizeof(test));
etherpci_min(data, (unsigned char *)&test[0], data->ETHER_BUF_START, sizeof(test));
for (i = 0; i < sizeof(test); i++) {
if (test[i] != i) {
dprintf("memory test failed: %02x %02x\n", i, test[i]);
return 0;
}
}
dprintf("ne2000 pci ethernet card found - ");
print_address(&data->myaddr);
return 1;
}
static void
init(etherpci_private_t *data)
{
int i;
#if STAY_ON_PAGE_0
ringzero(data, data->EC_RXBUF_START_PAGE, data->EC_RXBUF_END_PAGE);
#endif
ether_outb(data, EN0_DCFG, DCFG_BM16);
ether_outb(data, EN0_RCNTLO, 0x0);
ether_outb(data, EN0_RCNTHI, 0x0);
ether_outb(data, EN0_RXCR, ENRXCR_BCST);
ether_outb(data, EN0_TXCR, TXCR_LOOPBACK);
ether_outb(data, EN0_BOUNDARY, data->EC_RXBUF_END_PAGE);
data->boundary = data->EC_RXBUF_START_PAGE;
ether_outb(data, EN0_STARTPG, data->EC_RXBUF_START_PAGE);
ether_outb(data, EN0_STOPPG, data->EC_RXBUF_END_PAGE);
ether_outb(data, EN0_TPSR, data->EC_VMEM_PAGE);
ether_outb(data, EN0_ISR, 0xff);
ether_outb(data, EN0_IMR, INTS_WE_CARE_ABOUT);
ether_outb(data, EN_CCMD, ENC_NODMA | ENC_PAGE1);
for (i = 0; i < 6; i++) {
ether_outb(data, EN1_PHYS + i, data->myaddr.ebyte[i]);
}
for (i = 0; i < 8; i++) {
ether_outb(data, EN1_MULT+i, 0xff);
}
data->nmulti = 0;
ether_outb(data, EN1_CURPAG, data->EC_RXBUF_START_PAGE);
ether_outb(data, EN_CCMD, ENC_START | ENC_PAGE0 | ENC_NODMA);
ether_outb(data, EN0_TXCR, 0x00);
}
static void
ringcopy(etherpci_private_t *data, unsigned char *ether_buf,
int offset, int len)
{
int roffset;
int rem;
roffset = offset - data->EC_RINGSTART;
rem = data->EC_RINGSIZE - roffset;
if (len > rem) {
etherpci_min(data, ðer_buf[0], offset, rem);
etherpci_min(data, ðer_buf[rem], data->EC_RINGSTART, len - rem);
} else {
etherpci_min(data, ðer_buf[0], offset, len);
}
}
static void
setboundary(etherpci_private_t *data, unsigned char nextboundary)
{
if (nextboundary != data->EC_RXBUF_START_PAGE) {
ether_outb(data, EN0_BOUNDARY, nextboundary - 1);
} else {
ether_outb(data, EN0_BOUNDARY, data->EC_RXBUF_END_PAGE - 1);
}
data->boundary = nextboundary;
}
static int
reset(etherpci_private_t *data)
{
unsigned char cmd;
int resend = false;
cmd = ether_inb(data, EN_CCMD);
ether_outb(data, EN_CCMD, ENC_STOP | ENC_NODMA);
snooze(10 * 1000);
ether_outb(data, EN0_RCNTLO, 0x0);
ether_outb(data, EN0_RCNTHI, 0x0);
if (cmd & ENC_TRANS) {
if(!(getisr(data) & (ISR_TRANSMIT | ISR_TRANSMIT_ERROR)))
resend = true;
}
ether_outb(data, EN0_TXCR, TXCR_LOOPBACK);
ether_outb(data, EN_CCMD, ENC_START | ENC_PAGE0 | ENC_NODMA);
return (resend);
}
static void
finish_reset(etherpci_private_t *data, int resend)
{
setisr(data, ISR_OVERWRITE);
ether_outb(data, EN0_TXCR, 0x00);
if (resend) {
ether_outb(data, EN_CCMD, ENC_START | ENC_PAGE0 | ENC_NODMA | ENC_TRANS);
}
}
static int32
etherpci_interrupt(void *_data)
{
etherpci_private_t *data = (etherpci_private_t *) _data;
unsigned char isr;
int wakeup_reader = 0;
int wakeup_writer = 0;
int32 handled = B_UNHANDLED_INTERRUPT;
data->ints++;
ETHER_DEBUG(INTERRUPT, data->debug, "ENTR isr=%x & %x?\n",getisr(data), INTS_WE_CARE_ABOUT);
for (INTR_LOCK(data, isr = getisr(data));
isr & INTS_WE_CARE_ABOUT;
INTR_LOCK(data, isr = getisr(data))) {
if (isr & ISR_RECEIVE) {
data->rints++;
wakeup_reader++;
INTR_LOCK(data, setisr(data, ISR_RECEIVE));
handled = B_HANDLED_INTERRUPT;
continue;
}
if (isr & ISR_TRANSMIT_ERROR) {
data->werrs++;
INTR_LOCK(data, setisr(data, ISR_TRANSMIT_ERROR));
wakeup_writer++;
handled = B_HANDLED_INTERRUPT;
continue;
}
if (isr & ISR_TRANSMIT) {
data->wints++;
INTR_LOCK(data, setisr(data, ISR_TRANSMIT));
wakeup_writer++;
handled = B_HANDLED_INTERRUPT;
continue;
}
if (isr & ISR_RECEIVE_ERROR) {
uint32 err_count;
err_count = ether_inb(data, EN0_CNTR0); data->frame_errs += err_count;
err_count = ether_inb(data, EN0_CNTR1); data->crc_errs += err_count;
err_count = ether_inb(data, EN0_CNTR2); data->frames_lost += err_count;
isr &= ~ISR_RECEIVE_ERROR;
INTR_LOCK(data, setisr(data, ISR_RECEIVE_ERROR));
handled = B_HANDLED_INTERRUPT;
}
if (isr & ISR_DMADONE) {
isr &= ~ISR_DMADONE;
handled = B_HANDLED_INTERRUPT;
}
if (isr & ISR_OVERWRITE) {
isr &= ~ISR_OVERWRITE;
handled = B_HANDLED_INTERRUPT;
}
if (isr & ISR_COUNTER) {
isr &= ~ISR_COUNTER;
INTR_LOCK(data, setisr(data, ISR_COUNTER));
handled = B_HANDLED_INTERRUPT;
}
if (isr) {
INTR_LOCK(data, setisr(data, isr));
data->interrs++;
}
}
if (wakeup_reader) {
input_unwait(data, 1);
}
if (wakeup_writer) {
output_unwait(data, 1);
}
return handled;
}
static void
check_errors(etherpci_private_t *data)
{
#define DOIT(stat, message) \
if (stat > stat##_last) { \
stat##_last = stat; \
dprintf(message, stat##_last); \
}
DOIT(data->rerrs, "Receive errors now %d\n");
DOIT(data->werrs, "Transmit errors now %d\n");
DOIT(data->interrs, "Interrupt errors now %d\n");
DOIT(data->frames_lost, "Frames lost now %d\n");
#undef DOIT
#if 0
DOIT(data->frame_errs, "Frame alignment errors now %d\n");
DOIT(data->crc_errs, "CRC errors now %d\n");
#endif
}
static int
more_packets(etherpci_private_t *data, int didreset)
{
#if STAY_ON_PAGE_0
unsigned offset;
ring_header ring;
offset = data->boundary << EC_PAGE_SHIFT;
etherpci_min(data, (unsigned char *)&ring, offset, sizeof(ring));
return ring.status && ring.next_packet && ring.count;
#else
cpu_status ps;
unsigned char cur;
ps = disable_interrupts();
intr_lock(data);
ether_outb(data, EN_CCMD, ENC_PAGE1);
cur = ether_inb(data, EN1_CURPAG);
ether_outb(data, EN_CCMD, ENC_PAGE0);
intr_unlock(data);
restore_interrupts(ps);
return didreset || cur != data->boundary;
#endif
}
static int
copy_packet(etherpci_private_t *data, unsigned char *ether_buf,
int buflen)
{
ring_header ring;
unsigned offset;
int len;
int rlen;
int ether_len = 0;
int didreset = 0;
int resend = 0;
io_lock(data);
check_errors(data);
if (getisr(data) & ISR_OVERWRITE) {
data->resets++;
resend = reset(data);
didreset++;
}
if (more_packets(data, didreset)) do {
offset = data->boundary << EC_PAGE_SHIFT;
etherpci_min(data, (unsigned char *)&ring, offset, sizeof(ring));
len = swapshort(ring.count);
if (!(ring.status & RSR_INTACT)) {
dprintf("packet not intact! (%02x,%u,%02x) (%d)\n",
ring.status, ring.next_packet, ring.count, data->boundary);
ether_len = 0; setboundary(data, ring.next_packet);
break;
}
if (ring.next_packet < data->EC_RXBUF_START_PAGE
|| ring.next_packet >= data->EC_RXBUF_END_PAGE) {
dprintf("etherpci_read: bad next packet! (%02x,%u,%02x) (%d)\n",
ring.status, ring.next_packet, ring.count, data->boundary);
data->rerrs++;
ether_len = 0; setboundary(data, ring.next_packet);
break;
}
len = swapshort(ring.count);
rlen = len - 4;
if (rlen < ETHER_MIN_SIZE || rlen > ETHER_MAX_SIZE) {
dprintf("etherpci_read: bad length! (%02x,%u,%02x) (%d)\n",
ring.status, ring.next_packet, ring.count, data->boundary);
data->rerrs++;
ether_len = 0; setboundary(data, ring.next_packet);
break;
}
if (rlen > buflen)
rlen = buflen;
ringcopy(data, ether_buf, offset + 4, rlen);
#if STAY_ON_PAGE_0
ringzero(data, data->boundary, ring.next_packet);
#endif
ether_len = rlen;
setboundary(data, ring.next_packet);
data->reads++;
} while (0);
if (didreset) {
dprintf("finishing reset!\n");
finish_reset(data, resend);
}
if (input_count(data) <= 0 && more_packets(data, didreset)) {
input_unwait(data, 1);
}
io_unlock(data);
return ether_len;
}
static int
my_packet(etherpci_private_t *data, char *addr)
{
unsigned int i;
const char broadcast[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
if (memcmp(addr, &data->myaddr, sizeof(data->myaddr)) == 0
|| memcmp(addr, broadcast, sizeof(broadcast)) == 0)
return 1;
for (i = 0; i < data->nmulti; i++) {
if (memcmp(addr, &data->multi[i], sizeof(data->multi[i])) == 0)
return 1;
}
return 0;
}
static status_t
enable_addressing(etherpci_private_t *data)
{
unsigned char cmd;
#if __i386__
data->reg_base = data->pciInfo->u.h0.base_registers[0];
#else
uint32 base, size, offset;
base = data->pciInfo->u.h0.base_registers[0];
size = data->pciInfo->u.h0.base_register_sizes[0];
base = base & ~(B_PAGE_SIZE-1);
offset = data->pciInfo->u.h0.base_registers[0] - base;
size += offset;
size = (size +(B_PAGE_SIZE-1)) & ~(B_PAGE_SIZE-1);
dprintf(kDevName ": PCI base=%x size=%x offset=%x\n", base, size, offset);
if ((data->ioarea = map_physical_memory(kDevName "_regs", base, size,
B_ANY_KERNEL_ADDRESS, B_READ_AREA | B_WRITE_AREA,
(void **)&data->reg_base)) < 0) {
return B_ERROR;
}
data->reg_base = data->reg_base + offset;
#endif
dprintf(kDevName ": reg_base=%" B_PRIx32 "\n", data->reg_base);
cmd = (gPCIModInfo->read_pci_config)(data->pciInfo->bus, data->pciInfo->device, data->pciInfo->function, PCI_command, 2);
(gPCIModInfo->write_pci_config)(data->pciInfo->bus, data->pciInfo->device, data->pciInfo->function, PCI_command, 2, cmd | PCI_command_io);
return B_OK;
}
static int
domulti(etherpci_private_t *data, char *addr)
{
int i;
int nmulti = data->nmulti;
if (nmulti == MAX_MULTI)
return B_ERROR;
for (i = 0; i < nmulti; i++) {
if (memcmp(&data->multi[i], addr, sizeof(data->multi[i])) == 0) {
break;
}
}
if (i == nmulti) {
memcpy(&data->multi[i], addr, sizeof(data->multi[i]));
data->nmulti++;
}
if (data->nmulti == 1) {
dprintf("Enabling multicast\n");
ether_outb(data, EN0_RXCR, ENRXCR_BCST | ENRXCR_MCST);
}
return B_NO_ERROR;
}
#if DEBUGGER_COMMAND
static int
etherpci(int argc, char **argv) {
uint16 i,j;
const char * usage = "usage: etherpci { Function_calls | PCI_IO | Stats | Rx_trace | Tx_trace }\n";
if (argc < 2) {
kprintf("%s",usage); return 0;
}
for (i= argc, j= 1; i > 1; i--, j++) {
switch (*argv[j]) {
case 'F':
case 'f':
gdev->debug ^= FUNCTION;
if (gdev->debug & FUNCTION)
kprintf("Function() call trace Enabled\n");
else
kprintf("Function() call trace Disabled\n");
break;
case 'N':
case 'n':
gdev->debug ^= SEQ;
if (gdev->debug & SEQ)
kprintf("Sequence numbers packet trace Enabled\n");
else
kprintf("Sequence numbers packet trace Disabled\n");
break;
case 'R':
case 'r':
gdev->debug ^= RX;
if (gdev->debug & RX)
kprintf("Receive packet trace Enabled\n");
else
kprintf("Receive packet trace Disabled\n");
break;
case 'T':
case 't':
gdev->debug ^= TX;
if (gdev->debug & TX)
kprintf("Transmit packet trace Enabled\n");
else
kprintf("Transmit packet trace Disabled\n");
break;
case 'S':
case 's':
kprintf(kDevName " statistics\n");
kprintf("rx_ints %d, tx_ints %d\n", gdev->rints, gdev->wints);
kprintf("resets %d \n", gdev->resets);
kprintf("crc_errs %d, frame_errs %d, frames_lost %d\n", gdev->crc_errs, gdev->frame_errs, gdev->frames_lost);
break;
case 'P':
case 'p':
gdev->debug ^= PCI_IO;
if (gdev->debug & PCI_IO)
kprintf("PCI IO trace Enabled\n");
else
kprintf("PCI IO trace Disabled\n");
break;
default:
kprintf("%s",usage);
return 0;
}
}
return 0;
}
#endif
static void
dump_packet(const char * msg, unsigned char * buf, uint16 size)
{
uint16 j;
dprintf("%s dumping %p size %u \n", msg, buf, size);
for (j = 0; j < size; j++) {
if ((j & 0xF) == 0)
dprintf("\n");
dprintf("%2.2x ", buf[j]);
}
}
status_t
init_hardware(void)
{
return B_NO_ERROR;
}
status_t
init_driver(void)
{
status_t status;
int32 entries;
char devName[64];
int32 i;
dprintf(kDevName ": init_driver ");
if ((status = get_module( B_PCI_MODULE_NAME, (module_info **)&gPCIModInfo )) != B_OK) {
dprintf(kDevName " Get module failed! %s\n", strerror(status ));
return status;
}
if ((entries = get_pci_list(gDevList, MAX_CARDS )) == 0) {
dprintf("init_driver: " kDevName " not found\n");
free_pci_list(gDevList);
put_module(B_PCI_MODULE_NAME );
return B_ERROR;
}
dprintf("\n");
for (i=0; i<entries; i++ )
{
sprintf(devName, "%s%" B_PRId32, kDevDir, i );
gDevNameList[i] = (char *)malloc(strlen(devName)+1);
strcpy(gDevNameList[i], devName);
}
gDevNameList[i] = NULL;
return B_OK;
}
void
uninit_driver(void)
{
void *item;
int32 i;
for (i = 0; (item = gDevNameList[i]) != NULL; i++) {
free(item);
}
free_pci_list(gDevList);
put_module(B_PCI_MODULE_NAME);
}
device_hooks *
find_device(const char *name)
{
int32 i;
char *item;
for (i = 0; (item = gDevNameList[i]) != NULL; i++) {
if (!strcmp(name, item))
return &gDeviceHooks;
}
return NULL;
}
const char**
publish_devices(void)
{
dprintf(kDevName ": publish_devices()\n");
return (const char **)gDevNameList;
}
static status_t
read_hook(void *_data, off_t pos, void *buf, size_t *len)
{
etherpci_private_t *data = (etherpci_private_t *) _data;
ulong buflen;
int packet_len;
buflen = *len;
atomic_add(&data->inrw, 1);
if (data->interrupted) {
atomic_add(&data->inrw, -1);
return B_INTERRUPTED;
}
do {
if (!data->nonblocking) {
input_wait(data);
}
if (data->interrupted) {
atomic_add(&data->inrw, -1);
return B_INTERRUPTED;
}
packet_len = copy_packet(data, (unsigned char *)buf, buflen);
if ((packet_len) && (data->debug & RX)) {
dump_packet("RX:" ,buf, packet_len);
}
} while (!data->nonblocking && packet_len == 0 && !my_packet(data, buf));
atomic_add(&data->inrw, -1);
*len = packet_len;
return 0;
}
static status_t
open_hook(const char *name, uint32 flags, void **cookie)
{
int32 devID;
int32 mask;
status_t status;
char *devName;
etherpci_private_t *data;
for (devID = 0; (devName = gDevNameList[devID]); devID++) {
if (strcmp(name, devName) == 0)
break;
}
if (!devName)
return EINVAL;
mask = 1 << devID;
if (atomic_or(&gOpenMask, mask) &mask)
return B_BUSY;
if (!(*cookie = data = (etherpci_private_t *)malloc(sizeof(etherpci_private_t)))) {
status = B_NO_MEMORY;
goto err0;
}
memset(data, 0, sizeof(etherpci_private_t));
data->pciInfo = gDevList[devID];
data->devID = devID;
data->interrupted = 0;
data->inrw = 0;
data->nonblocking = 0;
data->debug = DEFAULT_DEBUG_FLAGS;
ETHER_DEBUG(FUNCTION, data->debug, kDevName ": open %s dev=%p\n", name, data);
#if DEBUGGER_COMMAND
gdev = data;
add_debugger_command (kDevName, etherpci, "Ethernet driver Info");
#endif
if ((status = enable_addressing(data)) != B_OK)
goto err1;
if (!probe(data)) {
dprintf(kDevName ": probe failed\n");
goto err1;
}
if (create_sems(data) != B_OK)
goto err2;
install_io_interrupt_handler( data->pciInfo->u.h0.interrupt_line, etherpci_interrupt, *cookie, 0 );
dprintf("Interrupts installed at %x\n", data->pciInfo->u.h0.interrupt_line);
init(data);
return B_NO_ERROR;
err2:
#if !__i386__
delete_area(data->ioarea);
#endif
err1:
#if DEBUGGER_COMMAND
remove_debugger_command (kDevName, etherpci);
#endif
free(data);
err0:
atomic_and(&gOpenMask, ~mask);
dprintf(kDevName ": open failed!\n");
return B_ERROR;
}
static status_t
close_hook(void *_data)
{
etherpci_private_t *data = (etherpci_private_t *)_data;
ETHER_DEBUG(FUNCTION, data->debug, kDevName ": close dev=%p\n", data);
io_lock(data);
data->interrupted = 1;
input_unwait(data, 1);
output_unwait(data, 1);
io_unlock(data);
while (inrw(data)) {
snooze(1000000);
dprintf("ether: still waiting for read/write to finish\n");
}
ether_outb(data, EN_CCMD, ENC_STOP);
snooze(2000);
remove_io_interrupt_handler(data->pciInfo->u.h0.interrupt_line, etherpci_interrupt, data);
delete_sem(data->iolock);
delete_sem(data->ilock);
delete_sem(data->olock);
data->ints = 0;
data->rints = 0;
data->rerrs = 0;
data->wints = 0;
data->werrs = 0;
data->reads = 0;
data->writes = 0;
data->interrs = 0;
data->resets = 0;
data->frame_errs = 0;
data->crc_errs = 0;
data->frames_lost = 0;
data->rerrs_last = 0;
data->werrs_last = 0;
data->interrs_last = 0;
data->frame_errs_last = 0;
data->crc_errs_last = 0;
data->frames_lost_last = 0;
data->chip_rx_frame_errors = 0;
data->chip_rx_crc_errors = 0;
data->chip_rx_missed_errors = 0;
#if DEBUGGER_COMMAND
remove_debugger_command (kDevName, etherpci);
#endif
return B_OK;
}
static status_t
free_hook(void *_data)
{
etherpci_private_t *data = (etherpci_private_t *)_data;
int32 mask;
ETHER_DEBUG(FUNCTION, data->debug, kDevName ": free dev=%p\n", data);
#if !__i386__
delete_area(data->ioarea);
#endif
mask = 1L << data->devID;
atomic_and(&gOpenMask, ~mask);
free(data);
return B_OK;
}
static status_t
write_hook(void *_data, off_t pos, const void *buf, size_t *len)
{
etherpci_private_t *data = (etherpci_private_t *) _data;
ulong buflen;
int status;
buflen = *len;
atomic_add(&data->inrw, 1);
if (data->interrupted) {
atomic_add(&data->inrw, -1);
return B_INTERRUPTED;
}
status = output_wait(data, ETHER_TRANSMIT_TIMEOUT);
if (status < B_NO_ERROR || data->interrupted) {
atomic_add(&data->inrw, -1);
return status;
}
io_lock(data);
check_errors(data);
if (data->writes > 0)
check_transmit_status(data);
etherpci_mout(data, data->ETHER_BUF_START, (const unsigned char *)buf, buflen);
if (buflen < ETHER_MIN_SIZE) {
buflen = ETHER_MIN_SIZE;
}
ether_outb(data, EN0_TCNTLO, (char)(buflen & 0xff));
ether_outb(data, EN0_TCNTHI, (char)(buflen >> 8));
ether_outb(data, EN_CCMD, ENC_NODMA | ENC_TRANS);
data->writes++;
io_unlock(data);
atomic_add(&data->inrw, -1);
*len = buflen;
if (data->debug & TX)
dump_packet("TX:",(unsigned char *) buf, buflen);
return 0;
}
static status_t
control_hook(void *_data, uint32 msg, void *buf, size_t len)
{
etherpci_private_t *data = (etherpci_private_t *) _data;
switch (msg) {
case ETHER_INIT:
ETHER_DEBUG(FUNCTION, data->debug, kDevName ": control: ETHER_INIT \n");
return B_OK;
case ETHER_GETADDR:
if (data == NULL)
return B_ERROR;
ETHER_DEBUG(FUNCTION, data->debug, kDevName ": control: GET_ADDR \n");
memcpy(buf, &data->myaddr, sizeof(data->myaddr));
return B_OK;
case ETHER_NONBLOCK:
if (data == NULL)
return B_ERROR;
memcpy(&data->nonblocking, buf, sizeof(data->nonblocking));
ETHER_DEBUG(FUNCTION, data->debug, kDevName ": control: NON_BLOCK %x\n", data->nonblocking);
return B_OK;
case ETHER_ADDMULTI:
ETHER_DEBUG(FUNCTION, data->debug, kDevName ": control: DO_MULTI\n");
return domulti(data, (char *)buf);
}
return B_ERROR;
}