3b2: Improve NI performance

This change implements asynchronous (non-polling) mode for NI Ethernet
packet receive.
This commit is contained in:
Seth Morabito 2019-08-25 20:39:48 -07:00
parent 9539b6273b
commit e4e7071b6a
3 changed files with 66 additions and 114 deletions

View file

@ -1861,6 +1861,8 @@ t_stat sim_instr(void)
cpu_exception_stack_depth--; cpu_exception_stack_depth--;
} }
AIO_CHECK_EVENT;
if (sim_interval-- <= 0) { if (sim_interval-- <= 0) {
if ((stop_reason = sim_process_event())) { if ((stop_reason = sim_process_event())) {
break; break;

View file

@ -135,8 +135,8 @@ static const uint32 NI_DIAG_CRCS[] = {
*/ */
UNIT ni_unit[] = { UNIT ni_unit[] = {
{ UDATA(&ni_rcv_svc, UNIT_IDLE|UNIT_ATTABLE, 0) }, { UDATA(&ni_rcv_svc, UNIT_IDLE|UNIT_ATTABLE, 0) },
{ UDATA(&ni_sanity_svc, UNIT_DIS, 0) }, { UDATA(&ni_sanity_svc, UNIT_IDLE|UNIT_DIS, 0) },
{ UDATA(&ni_rq_svc, UNIT_DIS, 0) }, { UDATA(&ni_rq_svc, UNIT_IDLE|UNIT_DIS, 0) },
{ UDATA(&ni_cio_svc, UNIT_DIS, 0) }, { UDATA(&ni_cio_svc, UNIT_DIS, 0) },
{ 0 } { 0 }
}; };
@ -262,8 +262,6 @@ static void ni_enable()
sim_debug(DBG_TRACE, &ni_dev, sim_debug(DBG_TRACE, &ni_dev,
"[ni_enable] Enabling the interface.\n"); "[ni_enable] Enabling the interface.\n");
ni.callback = ni_recv_callback;
/* Reset Statistics */ /* Reset Statistics */
memset(&ni.stats, 0, sizeof(ni_stat_info)); memset(&ni.stats, 0, sizeof(ni_stat_info));
@ -273,10 +271,10 @@ static void ni_enable()
/* Enter fast polling mode */ /* Enter fast polling mode */
ni.poll_rate = NI_QPOLL_FAST; ni.poll_rate = NI_QPOLL_FAST;
/* Schedule the queue poller in fast poll mode */ /* Start the queue poller in fast poll mode */
sim_activate_abs(rq_unit, NI_QPOLL_FAST); sim_activate_abs(rq_unit, NI_QPOLL_FAST);
/* Schedule the sanity timer */ /* Start the sanity timer */
sim_activate_after(sanity_unit, NI_SANITY_INTERVAL_US); sim_activate_after(sanity_unit, NI_SANITY_INTERVAL_US);
/* Enable the interface */ /* Enable the interface */
@ -287,7 +285,6 @@ static void ni_disable()
{ {
sim_debug(DBG_TRACE, &ni_dev, sim_debug(DBG_TRACE, &ni_dev,
"[ni_disable] Disabling the interface.\n"); "[ni_disable] Disabling the interface.\n");
ethq_clear(&ni.readq);
ni.enabled = FALSE; ni.enabled = FALSE;
cio[ni.cid].intr = FALSE; cio[ni.cid].intr = FALSE;
sim_cancel(ni_unit); sim_cancel(ni_unit);
@ -295,7 +292,6 @@ static void ni_disable()
sim_cancel(rq_unit); sim_cancel(rq_unit);
sim_cancel(cio_unit); sim_cancel(cio_unit);
sim_cancel(sanity_unit); sim_cancel(sanity_unit);
ni.callback = NULL;
} }
static void ni_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp) static void ni_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp)
@ -316,7 +312,7 @@ static void ni_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp
cio[cid].op = rentry->opcode; cio[cid].op = rentry->opcode;
delay = NI_INT_DELAY_US; delay = NI_INT_DELAY;
switch(rentry->opcode) { switch(rentry->opcode) {
case CIO_DLM: case CIO_DLM:
@ -426,12 +422,6 @@ static void ni_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp
sim_debug(DBG_TRACE, &ni_dev, sim_debug(DBG_TRACE, &ni_dev,
"[ni_cmd] NI TURNON Operation\n"); "[ni_cmd] NI TURNON Operation\n");
if (sim_idle_enab) {
sim_clock_coschedule(rcv_unit, tmxr_poll);
} else {
sim_activate_after(rcv_unit, NI_RCV_POLL_US);
}
ni_enable(); ni_enable();
cio_cqueue(cid, CIO_STAT, NIQESIZE, &centry, app_data); cio_cqueue(cid, CIO_STAT, NIQESIZE, &centry, app_data);
@ -531,10 +521,10 @@ static void ni_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp
centry.byte_count <<= 8; centry.byte_count <<= 8;
} }
delay = 0;
cio_cqueue(cid, CIO_STAT, NIQESIZE, &centry, app_data); cio_cqueue(cid, CIO_STAT, NIQESIZE, &centry, app_data);
delay = 0;
break; break;
default: default:
sim_debug(DBG_TRACE, &ni_dev, sim_debug(DBG_TRACE, &ni_dev,
@ -546,7 +536,7 @@ static void ni_cmd(uint8 cid, cio_entry *rentry, uint8 *rapp_data, t_bool is_exp
break; break;
} }
sim_activate_after(cio_unit, delay); sim_activate_abs(cio_unit, delay);
} }
t_stat ni_setmac(UNIT *uptr, int32 val, CONST char* cptr, void* desc) t_stat ni_setmac(UNIT *uptr, int32 val, CONST char* cptr, void* desc)
@ -633,7 +623,7 @@ void ni_sysgen(uint8 cid)
* sysgen'ed again later. */ * sysgen'ed again later. */
ni.crc = 0; ni.crc = 0;
sim_activate_after(cio_unit, NI_INT_DELAY_US); sim_activate_abs(cio_unit, NI_INT_DELAY);
} }
/* /*
@ -742,17 +732,10 @@ t_stat ni_reset(DEVICE *dptr)
/* Set an initial MAC address in the AT&T NI range */ /* Set an initial MAC address in the AT&T NI range */
ni_setmac(ni_dev.units, 0, "80:00:10:03:00:00/32", NULL); ni_setmac(ni_dev.units, 0, "80:00:10:03:00:00/32", NULL);
/* Initialize the receive queue one time only */
status = ethq_init(&ni.readq, NI_QUE_MAX);
if (status != SCPE_OK) {
sim_debug(DBG_TRACE, &ni_dev,
"[ni_reset] Failure: ethq_init status=%d", status);
return status;
}
ni.initialized = TRUE; ni.initialized = TRUE;
} }
/* Set up unit names */
snprintf(uname, 16, "%s-RCV", dptr->name); snprintf(uname, 16, "%s-RCV", dptr->name);
sim_set_uname(rcv_unit, uname); sim_set_uname(rcv_unit, uname);
snprintf(uname, 16, "%s-SANITY", dptr->name); snprintf(uname, 16, "%s-SANITY", dptr->name);
@ -781,28 +764,23 @@ t_stat ni_reset(DEVICE *dptr)
t_stat ni_rcv_svc(UNIT *uptr) t_stat ni_rcv_svc(UNIT *uptr)
{ {
t_stat status; t_stat read_succ;
UNUSED(uptr); UNUSED(uptr);
/* If a CIO interrupt is alrady pending, skip this read */ /* Since we cannot know which queue (large packet or small packet
if ((rcv_unit->flags & UNIT_ATT) && !cio[ni.cid].intr) { * queue) will have room for the next packet that we read, for
/* Try to receive a packet */ * safety reasons we will not call eth_read() until we're certain
do { * there's room available in BOTH queues. */
status = eth_read(ni.eth, &ni.rd_buf, ni.callback); while (ni.enabled && NI_BUFFERS_AVAIL) {
} while (status); read_succ = eth_read(ni.eth, &ni.rd_buf, NULL);
if (!read_succ) {
/* Attempt to process a packet from the queue */ break;
}
/* Attempt to process the packet that was received. */
ni_process_packet(); ni_process_packet();
} }
/* Re-schedule the next poll */
if (sim_idle_enab) {
sim_clock_coschedule(rcv_unit, tmxr_poll);
} else {
sim_activate_after(rcv_unit, NI_RCV_POLL_US);
}
return SCPE_OK; return SCPE_OK;
} }
@ -810,15 +788,16 @@ t_stat ni_rcv_svc(UNIT *uptr)
* Service used by the card to poll for available request queue * Service used by the card to poll for available request queue
* entries. * entries.
*/ */
t_stat ni_rq_svc(UNIT *uptr) t_stat ni_rq_svc(UNIT *uptr)
{ {
t_bool rq_taken;
int i, wp, no_rque; int i, wp, no_rque;
cio_entry rqe = {0}; cio_entry rqe = {0};
uint8 slot[4] = {0}; uint8 slot[4] = {0};
UNUSED(uptr); UNUSED(uptr);
rq_taken = FALSE;
no_rque = cio[ni.cid].no_rque - 1; no_rque = cio[ni.cid].no_rque - 1;
for (i = 0; i < no_rque; i++) { for (i = 0; i < no_rque; i++) {
@ -835,6 +814,7 @@ t_stat ni_rq_svc(UNIT *uptr)
ni.job_cache[i].req[wp].slot = slot[3]; ni.job_cache[i].req[wp].slot = slot[3];
ni.job_cache[i].wp = (wp + 1) % NI_CACHE_LEN; ni.job_cache[i].wp = (wp + 1) % NI_CACHE_LEN;
ni.stats.rq_taken++; ni.stats.rq_taken++;
rq_taken = TRUE;
} }
} }
@ -845,6 +825,13 @@ t_stat ni_rq_svc(UNIT *uptr)
ni.poll_rate = NI_QPOLL_SLOW; ni.poll_rate = NI_QPOLL_SLOW;
} }
/* If any receive jobs were found, schedule a packet read right
away */
if (rq_taken) {
sim_activate_abs(rcv_unit, 0);
}
/* Reactivate the poller. */
if (ni.poll_rate == NI_QPOLL_FAST) { if (ni.poll_rate == NI_QPOLL_FAST) {
sim_activate_abs(rq_unit, NI_QPOLL_FAST); sim_activate_abs(rq_unit, NI_QPOLL_FAST);
} else { } else {
@ -904,46 +891,31 @@ t_stat ni_cio_svc(UNIT *uptr)
return SCPE_OK; return SCPE_OK;
} }
/*
* Do the work of trying to process the most recently received packet
*/
void ni_process_packet() void ni_process_packet()
{ {
int i, rp; int i, rp;
uint32 addr; uint32 addr;
uint8 slot; uint8 slot;
ETH_ITEM *item;
cio_entry centry = {0}; cio_entry centry = {0};
uint8 capp_data[4] = {0}; uint8 capp_data[4] = {0};
int len = 0; int len = 0;
int que_num = 0; int que_num = 0;
uint8 *rbuf; uint8 *rbuf;
/* If a CIO interrupt is alrady pending, the card is disabled, or len = ni.rd_buf.len;
if there's nothing to do, abort */ rbuf = ni.rd_buf.msg;
if (cio[ni.cid].intr || !ni.enabled || ni.readq.count == 0) { que_num = len > SM_PKT_MAX ? LG_QUEUE : SM_QUEUE;
return;
}
item = &ni.readq.item[ni.readq.head];
len = item->packet.len;
rbuf = item->packet.msg;
que_num = len > SM_PKT_MAX ? 1 : 0;
sim_debug(DBG_IO, &ni_dev, sim_debug(DBG_IO, &ni_dev,
"[ni_process_packet] Receiving a packet of size %d (0x%x)\n", "[ni_process_packet] Receiving a packet of size %d (0x%x)\n",
len, len); len, len);
/* /* Availability of a job in the job cache was checked before
* Consult the cache for a job. * calling ni_process_packet(), so there is no need to check it
*/ * again. */
if (ni.job_cache[que_num].rp == ni.job_cache[que_num].wp) {
sim_debug(DBG_ERR, &ni_dev,
"Job cache is empty.\n");
return;
}
/*
* Now that we know we have a job available, take it.
*/
rp = ni.job_cache[que_num].rp; rp = ni.job_cache[que_num].rp;
addr = ni.job_cache[que_num].req[rp].addr; addr = ni.job_cache[que_num].req[rp].addr;
slot = ni.job_cache[que_num].req[rp].slot; slot = ni.job_cache[que_num].req[rp].slot;
@ -962,8 +934,9 @@ void ni_process_packet()
} }
if (ni_dev.dctrl & DBG_DAT) { if (ni_dev.dctrl & DBG_DAT) {
dump_packet("RCV", &item->packet); dump_packet("RCV", &ni.rd_buf);
} }
ni.stats.rx_pkt++; ni.stats.rx_pkt++;
ni.stats.rx_bytes += len; ni.stats.rx_bytes += len;
@ -977,34 +950,12 @@ void ni_process_packet()
/* TODO: We should probably also check status here. */ /* TODO: We should probably also check status here. */
cio_cqueue(ni.cid, CIO_STAT, NIQESIZE, &centry, capp_data); cio_cqueue(ni.cid, CIO_STAT, NIQESIZE, &centry, capp_data);
/* Take the packet just processed off the queue */ /* Trigger an interrupt */
ethq_remove(&ni.readq);
/* And interrupt */
if (cio[ni.cid].ivec > 0) { if (cio[ni.cid].ivec > 0) {
cio[ni.cid].intr = TRUE; cio[ni.cid].intr = TRUE;
} }
} }
/*
* Called when the eth_read is complete.
*
* We can receive packets faster than we can process them, so we just put them
* in a queue and handle them in ni_rcv_svc.
*/
void ni_recv_callback(int status) {
UNUSED(status);
if (ni.enabled) {
sim_debug(DBG_IO, &ni_dev,
"[ni_recv_callback] inserting packet of len=%d (used=%d) into read queue.\n",
ni.rd_buf.len, ni.rd_buf.used);
ethq_insert(&ni.readq, ETH_ITM_NORMAL, &ni.rd_buf, SCPE_OK);
} else {
sim_debug(DBG_IO, &ni_dev, "[ni_recv_callback] Disabled. Ignoring packet.\n");
}
}
t_stat ni_attach(UNIT *uptr, CONST char *cptr) t_stat ni_attach(UNIT *uptr, CONST char *cptr)
{ {
t_stat status; t_stat status;
@ -1029,6 +980,7 @@ t_stat ni_attach(UNIT *uptr, CONST char *cptr)
sim_debug(DBG_ERR, &ni_dev, "ni_attach failure: open\n"); sim_debug(DBG_ERR, &ni_dev, "ni_attach failure: open\n");
free(tptr); free(tptr);
free(ni.eth); free(ni.eth);
ni.eth = NULL;
return status; return status;
} }
@ -1038,27 +990,27 @@ t_stat ni_attach(UNIT *uptr, CONST char *cptr)
eth_close(ni.eth); eth_close(ni.eth);
free(tptr); free(tptr);
free(ni.eth); free(ni.eth);
ni.eth = NULL;
return status;
}
/* Ensure the ethernet device is in async mode */
/* TODO: Determine best latency */
status = eth_set_async(ni.eth, 1000);
if (status != SCPE_OK) {
sim_debug(DBG_ERR, &ni_dev, "ni_attach failure: eth_set_async\n");
eth_close(ni.eth);
free(tptr);
free(ni.eth);
ni.eth = NULL;
return status; return status;
} }
uptr->filename = tptr; uptr->filename = tptr;
uptr->flags |= UNIT_ATT; uptr->flags |= UNIT_ATT;
status = ethq_init(&ni.readq, NI_QUE_MAX);
if (status != SCPE_OK) {
sim_debug(DBG_ERR, &ni_dev, "ni_attach failure: ethq init\n");
eth_close(ni.eth);
free(tptr);
free(ni.eth);
uptr->filename = NULL;
uptr->flags &= ~UNIT_ATT;
return status;
}
eth_filter(ni.eth, ni.filter_count, ni.macs, 0, 0); eth_filter(ni.eth, ni.filter_count, ni.macs, 0, 0);
/* ni_reset(&ni_dev); */
return SCPE_OK; return SCPE_OK;
} }
@ -1117,7 +1069,6 @@ t_stat ni_show_stats(FILE* st, UNIT* uptr, int32 val, CONST void* desc)
fprintf(st, "NI Ethernet statistics:\n"); fprintf(st, "NI Ethernet statistics:\n");
fprintf(st, fmt, "Recv:", ni.stats.rx_pkt); fprintf(st, fmt, "Recv:", ni.stats.rx_pkt);
fprintf(st, fmt, "Recv Bytes:", ni.stats.rx_bytes); fprintf(st, fmt, "Recv Bytes:", ni.stats.rx_bytes);
fprintf(st, fmt, "Dropped:", ni.stats.rx_dropped + ni.readq.loss);
fprintf(st, fmt, "Xmit:", ni.stats.tx_pkt); fprintf(st, fmt, "Xmit:", ni.stats.tx_pkt);
fprintf(st, fmt, "Xmit Bytes:", ni.stats.tx_bytes); fprintf(st, fmt, "Xmit Bytes:", ni.stats.tx_bytes);
fprintf(st, fmt, "Xmit Fail:", ni.stats.tx_fail); fprintf(st, fmt, "Xmit Fail:", ni.stats.tx_fail);

View file

@ -54,10 +54,7 @@
#define NIQESIZE 12 #define NIQESIZE 12
#define NI_QUE_MAX 1024 #define NI_QUE_MAX 1024
#define NI_TMR_WAIT_DEFAULT 3000 #define NI_INT_DELAY 10000
#define NI_RCV_POLL_US 8000
#define NI_XMIT_DELAY_US 60000
#define NI_INT_DELAY_US 500
#define NI_SANITY_INTERVAL_US 5000000 #define NI_SANITY_INTERVAL_US 5000000
/* Maximum allowed number of multicast addresses */ /* Maximum allowed number of multicast addresses */
@ -102,9 +99,9 @@
* packets up to 1500 bytes (no jumbo frames allowed) * packets up to 1500 bytes (no jumbo frames allowed)
*/ */
#define GE_QUEUE 0 /* General request queue */ #define GE_QUEUE 0 /* General request CIO queue */
#define SM_QUEUE 1 /* Small packet receive queue */ #define SM_QUEUE 0 /* Small packet receive queue number */
#define LG_QUEUE 2 /* Large packet receive queue */ #define LG_QUEUE 1 /* Large packet receive queue number */
#define SM_PKT_MAX 106 /* Max size of small packets (excluding CRC) */ #define SM_PKT_MAX 106 /* Max size of small packets (excluding CRC) */
#define LG_PKT_MAX 1514 /* Max size of large packets (excluding CRC) */ #define LG_PKT_MAX 1514 /* Max size of large packets (excluding CRC) */
@ -121,6 +118,9 @@
#define CHAR(c) ((((c) >= 0x20) && ((c) < 0x7f)) ? (c) : '.') #define CHAR(c) ((((c) >= 0x20) && ((c) < 0x7f)) ? (c) : '.')
#define NI_CACHE_HAS_SPACE(i) (((ni.job_cache[(i)].wp + 1) % NI_CACHE_LEN) != ni.job_cache[(i)].rp) #define NI_CACHE_HAS_SPACE(i) (((ni.job_cache[(i)].wp + 1) % NI_CACHE_LEN) != ni.job_cache[(i)].rp)
/* Determine whether both job caches have available slots */
#define NI_BUFFERS_AVAIL ((ni.job_cache[0].wp != ni.job_cache[0].rp) && \
(ni.job_cache[1].wp != ni.job_cache[1].rp))
/* /*
* The NI card caches up to three jobs taken from each of the two * The NI card caches up to three jobs taken from each of the two
@ -183,7 +183,6 @@ typedef struct {
ETH_DEV* eth; ETH_DEV* eth;
ETH_PACK rd_buf; ETH_PACK rd_buf;
ETH_PACK wr_buf; ETH_PACK wr_buf;
ETH_QUE readq;
ETH_MAC macs[NI_FILTER_MAX]; /* List of all filter addresses */ ETH_MAC macs[NI_FILTER_MAX]; /* List of all filter addresses */
int filter_count; /* Number of filters available */ int filter_count; /* Number of filters available */
ETH_PCALLBACK callback; ETH_PCALLBACK callback;