minix/drivers/dpeth/dp.c
Cristiano Giuffrida 48c6bb79f4 Driver refactory for live update and crash recovery.
SYSLIB CHANGES:
- DS calls to publish / retrieve labels consider endpoints instead of u32_t.

VFS CHANGES:
- mapdriver() only adds an entry in the dmap table in VFS.
- dev_up() is only executed upon reception of a driver up event.

INET CHANGES:
- INET no longer searches for existing drivers instances at startup.
- A newtwork driver is (re)initialized upon reception of a driver up event.
- Networking startup is now race-free by design. No need to waste 5 seconds
at startup any more.

DRIVER CHANGES:
- Every driver publishes driver up events when starting for the first time or
in case of restart when recovery actions must be taken in the upper layers.
- Driver up events are published by drivers through DS. 
- For regular drivers, VFS is normally the only subscriber, but not necessarily.
For instance, when the filter driver is in use, it must subscribe to driver
up events to initiate recovery.
- For network drivers, inet is the only subscriber for now.
- Every VFS driver is statically linked with libdriver, every network driver
is statically linked with libnetdriver.

DRIVER LIBRARIES CHANGES:
- Libdriver is extended to provide generic receive() and ds_publish() interfaces
for VFS drivers.
- driver_receive() is a wrapper for sef_receive() also used in driver_task()
to discard spurious messages that were meant to be delivered to a previous
version of the driver.
- driver_receive_mq() is the same as driver_receive() but integrates support
for queued messages.
- driver_announce() publishes a driver up event for VFS drivers and marks
the driver as initialized and expecting a DEV_OPEN message.
- Libnetdriver is introduced to provide similar receive() and ds_publish()
interfaces for network drivers (netdriver_announce() and netdriver_receive()).
- Network drivers all support live update with no state transfer now.

KERNEL CHANGES:
- Added kernel call statectl for state management. Used by driver_announce() to
unblock eventual callers sendrecing to the driver.
2010-04-08 13:41:35 +00:00

715 lines
19 KiB
C

#include <assert.h>
/*
** File: dp.c Version 1.01, Oct. 17, 2007
** Original: eth.c Version 1.00, Jan. 14, 1997
**
** Author: Giovanni Falzoni <gfalzoni@inwind.it>
**
** This file contains the ethernet device driver main task.
** It has to be integrated with the board specific drivers.
** It is a rewriting of Minix 2.0.0 ethernet driver (dp8390.c)
** to remove bord specific code. It should operate (I hope)
** with any board driver.
**
** The valid messages and their parameters are:
**
** m_type DL_PORT DL_PROC DL_COUNT DL_MODE DL_ADDR
** +------------+---------+---------+--------+-------+---------+
** | NOTIFY from HARDWARE, CLOCK, TTY, RS, PM, SYSTEM |
** +------------+---------+---------+--------+-------+---------+
** | HARD_STOP | | | | | |
** +------------+---------+---------+--------+-------+---------+
** | DL_WRITE | port nr | proc nr | count | mode | address | (3)
** +------------+---------+---------+--------+-------+---------+
** | DL_WRITEV | port nr | proc nr | count | mode | address | (4)
** +------------+---------+---------+--------+-------+---------+
** | DL_READ | port nr | proc nr | count | | address | (5)
** +------------+---------+---------+--------+-------+---------+
** | DL_READV | port nr | proc nr | count | | address | (6)
** +------------+---------+---------+--------+-------+---------+
** | DL_CONF | port nr | proc nr | | mode | address | (7)
** +------------+---------+---------+--------+-------+---------+
** | DL_STOP | port_nr | | | | | (8)
** +------------+---------+---------+--------+-------+---------+
** | DL_GETSTAT | port nr | proc nr | | | address | (9)
** +------------+---------+---------+--------+-------+---------+
**
** The messages sent are:
**
** m-type DL_PORT DL_PROC DL_COUNT DL_STAT DL_CLCK
** +------------+---------+---------+--------+---------+---------+
** |DL_TASK_REPL| port nr | proc nr |rd-count| err|stat| clock | (21)
** +------------+---------+---------+--------+---------+---------+
**
** m_type m3_i1 m3_i2 m3_ca1
** +------------+---------+---------+---------------+
** |DL_CONF_REPL| port nr |last port| ethernet addr | (20)
** +------------+---------+---------+---------------+
**
** $Id$
**
** 2007-10-17: modified by jfdsmit@gmail.com
** added a third argument to the reply() function because not
** every reply should be of DL_TASK_REPLY (one should be
** DL_STAT_REPLY)
*/
#include <minix/drivers.h>
#include <minix/netdriver.h>
#include <minix/endpoint.h>
#include <minix/ds.h>
#include <net/gen/ether.h>
#include <net/gen/eth_io.h>
#include "dp.h"
/*
** Local data
*/
extern int errno;
static dpeth_t de_table[DE_PORT_NR];
static const char *progname;
typedef struct dp_conf { /* Configuration description structure */
port_t dpc_port;
int dpc_irq;
phys_bytes dpc_mem;
char *dpc_envvar;
} dp_conf_t;
/* Device default configuration */
static dp_conf_t dp_conf[DE_PORT_NR] = {
/* I/O port, IRQ, Buff addr, Env. var, Buf. selector */
{ 0x300, 5, 0xC8000, "DPETH0", },
{ 0x280, 10, 0xCC000, "DPETH1", },
};
static char CopyErrMsg[] = "unable to read/write user data";
static char PortErrMsg[] = "illegal port";
static char RecvErrMsg[] = "netdriver_receive failed";
static char SendErrMsg[] = "send failed";
static char SizeErrMsg[] = "illegal packet size";
static char TypeErrMsg[] = "illegal message type";
static char DevName[] = "eth#?";
static void do_getname(message *mp);
/*
** Name: void reply(dpeth_t *dep, int err, int m_type)
** Function: Fills a reply message and sends it.
*/
static void reply(dpeth_t * dep, int err, int m_type)
{
message reply;
int status = FALSE;
if (dep->de_flags & DEF_ACK_SEND) status |= DL_PACK_SEND;
if (dep->de_flags & DEF_ACK_RECV) status |= DL_PACK_RECV;
reply.m_type = m_type;
reply.DL_PORT = dep - de_table;
reply.DL_PROC = dep->de_client;
reply.DL_STAT = status /* | ((u32_t) err << 16) */;
reply.DL_COUNT = dep->de_read_s;
getuptime(&reply.DL_CLCK);
DEBUG(printf("\t reply %d (%ld)\n", reply.m_type, reply.DL_STAT));
if ((status = send(dep->de_client, &reply)) == OK) {
dep->de_read_s = 0;
dep->de_flags &= NOT(DEF_ACK_SEND | DEF_ACK_RECV);
} else if (status != ELOCKED || err == OK)
panic(SendErrMsg, status);
return;
}
/*
** Name: void dp_confaddr(dpeth_t *dep)
** Function: Checks environment for a User defined ethernet address.
*/
static void dp_confaddr(dpeth_t * dep)
{
static char ea_fmt[] = "x:x:x:x:x:x";
char ea_key[16];
int ix;
long val;
strcpy(ea_key, dp_conf[dep - de_table].dpc_envvar);
strcat(ea_key, "_EA");
for (ix = 0; ix < SA_ADDR_LEN; ix++) {
val = dep->de_address.ea_addr[ix];
if (env_parse(ea_key, ea_fmt, ix, &val, 0x00L, 0xFFL) != EP_SET)
break;
dep->de_address.ea_addr[ix] = val;
}
if (ix != 0 && ix != SA_ADDR_LEN)
/* It's all or nothing, force a panic */
env_parse(ea_key, "?", 0, &val, 0L, 0L);
return;
}
/*
** Name: void update_conf(dpeth_t *dep, dp_conf_t *dcp)
** Function: Gets the default settings from 'dp_conf' table and
** modifies them from the environment.
*/
static void update_conf(dpeth_t * dep, const dp_conf_t * dcp)
{
static char dpc_fmt[] = "x:d:x";
long val;
dep->de_mode = DEM_SINK;
val = dcp->dpc_port; /* Get I/O port address */
switch (env_parse(dcp->dpc_envvar, dpc_fmt, 0, &val, 0x000L, 0x3FFL)) {
case EP_OFF: dep->de_mode = DEM_DISABLED; break;
case EP_ON:
case EP_SET: dep->de_mode = DEM_ENABLED; break;
}
dep->de_base_port = val;
val = dcp->dpc_irq | DEI_DEFAULT; /* Get Interrupt line (IRQ) */
env_parse(dcp->dpc_envvar, dpc_fmt, 1, &val, 0L, (long) NR_IRQ_VECTORS - 1);
dep->de_irq = val;
val = dcp->dpc_mem; /* Get shared memory address */
env_parse(dcp->dpc_envvar, dpc_fmt, 2, &val, 0L, LONG_MAX);
dep->de_linmem = val;
return;
}
/*
** Name: void do_dump(message *mp)
** Function: Displays statistics on screen (SFx key from console)
*/
static void do_dump(const message *mp)
{
dpeth_t *dep;
int port;
printf("\n\n");
for (port = 0, dep = de_table; port < DE_PORT_NR; port += 1, dep += 1) {
if (dep->de_mode == DEM_DISABLED) continue;
printf("%s statistics:\t\t", dep->de_name);
/* Network interface status */
printf("Status: 0x%04x (%d)\n\n", dep->de_flags, dep->de_int_pending);
(*dep->de_dumpstatsf) (dep);
/* Transmitted/received bytes */
printf("Tx bytes:%10ld\t", dep->bytes_Tx);
printf("Rx bytes:%10ld\n", dep->bytes_Rx);
/* Transmitted/received packets */
printf("Tx OK: %8ld\t", dep->de_stat.ets_packetT);
printf("Rx OK: %8ld\n", dep->de_stat.ets_packetR);
/* Transmit/receive errors */
printf("Tx Err: %8ld\t", dep->de_stat.ets_sendErr);
printf("Rx Err: %8ld\n", dep->de_stat.ets_recvErr);
/* Transmit unnerruns/receive overrruns */
printf("Tx Und: %8ld\t", dep->de_stat.ets_fifoUnder);
printf("Rx Ovr: %8ld\n", dep->de_stat.ets_fifoOver);
/* Transmit collisions/receive CRC errors */
printf("Tx Coll: %8ld\t", dep->de_stat.ets_collision);
printf("Rx CRC: %8ld\n", dep->de_stat.ets_CRCerr);
}
return;
}
/*
** Name: void get_userdata_s(int user_proc, vir_bytes user_addr, int count, void *loc_addr)
** Function: Copies data from user area.
*/
static void get_userdata_s(int user_proc, cp_grant_id_t grant,
vir_bytes offset, int count, void *loc_addr)
{
int rc;
vir_bytes len;
len = (count > IOVEC_NR ? IOVEC_NR : count) * sizeof(iovec_t);
if ((rc = sys_safecopyfrom(user_proc, grant, 0, (vir_bytes)loc_addr, len, D)) != OK)
panic(CopyErrMsg, rc);
return;
}
/*
** Name: void do_first_init(dpeth_t *dep, dp_conf_t *dcp);
** Function: Init action to setup task
*/
static void do_first_init(dpeth_t *dep, const dp_conf_t *dcp)
{
if (dep->de_linmem != 0) {
dep->de_memsegm = BIOS_SEG;
/* phys2seg(&dep->de_memsegm, &dep->de_memoffs, dep->de_linmem); */
} else
dep->de_linmem = 0xFFFF0000;
/* Make sure statisics are cleared */
memset((void *) &(dep->de_stat), 0, sizeof(eth_stat_t));
/* Device specific initialization */
(*dep->de_initf) (dep);
/* Set the interrupt handler policy. Request interrupts not to be reenabled
* automatically. Return the IRQ line number when an interrupt occurs.
*/
dep->de_hook = dep->de_irq;
sys_irqsetpolicy(dep->de_irq, 0 /*IRQ_REENABLE*/, &dep->de_hook);
dep->de_int_pending = FALSE;
sys_irqenable(&dep->de_hook);
return;
}
/*
** Name: void do_init(message *mp)
** Function: Checks for hardware presence.
** Provides initialization of hardware and data structures
*/
static void do_init(const message * mp)
{
int port;
dpeth_t *dep;
dp_conf_t *dcp;
message reply_mess;
const char *portname;
port = mp->DL_PORT;
if (port >= 0 && port < DE_PORT_NR) {
dep = &de_table[port];
dcp = &dp_conf[port];
strcpy(dep->de_name, DevName);
dep->de_name[4] = '0' + port;
portname = dep->de_name;
if (dep->de_mode == DEM_DISABLED) {
update_conf(dep, dcp); /* First time thru */
if (dep->de_mode == DEM_ENABLED &&
!el1_probe(dep) && /* Probe for 3c501 */
!wdeth_probe(dep) && /* Probe for WD80x3 */
!ne_probe(dep) && /* Probe for NEx000 */
!el2_probe(dep) && /* Probe for 3c503 */
!el3_probe(dep)) { /* Probe for 3c509 */
printf("%s: warning no ethernet card found at 0x%04X\n",
dep->de_name, dep->de_base_port);
dep->de_mode = DEM_DISABLED;
}
}
/* 'de_mode' may change if probe routines fail, test again */
switch (dep->de_mode) {
case DEM_DISABLED:
/* Device is configured OFF or hardware probe failed */
port = ENXIO;
break;
case DEM_ENABLED:
/* Device is present and probed */
if (dep->de_flags == DEF_EMPTY) {
/* These actions only the first time */
do_first_init(dep, dcp);
dep->de_flags |= DEF_ENABLED;
}
dep->de_flags &= NOT(DEF_PROMISC | DEF_MULTI | DEF_BROAD);
if (mp->DL_MODE & DL_PROMISC_REQ)
dep->de_flags |= DEF_PROMISC | DEF_MULTI | DEF_BROAD;
if (mp->DL_MODE & DL_MULTI_REQ) dep->de_flags |= DEF_MULTI;
if (mp->DL_MODE & DL_BROAD_REQ) dep->de_flags |= DEF_BROAD;
(*dep->de_flagsf) (dep);
dep->de_client = mp->m_source;
break;
case DEM_SINK:
/* Device not present (sink mode) */
memset(dep->de_address.ea_addr, 0, sizeof(ether_addr_t));
dp_confaddr(dep); /* Station address from env. */
break;
default: break;
}
*(ether_addr_t *) reply_mess.m3_ca1 = dep->de_address;
} else { /* Port number is out of range */
port = ENXIO;
portname = "(illegal dpeth port)";
}
reply_mess.m_type = DL_CONF_REPLY;
reply_mess.m3_i1 = port;
reply_mess.m3_i2 = DE_PORT_NR;
DEBUG(printf("\t reply %d\n", reply_mess.m_type));
if (send(mp->m_source, &reply_mess) != OK) /* Can't send */
panic(SendErrMsg, mp->m_source);
return;
}
/*
** Name: void dp_next_iovec(iovec_dat_t *iovp)
** Function: Retrieves data from next iovec element.
*/
PUBLIC void dp_next_iovec(iovec_dat_s_t * iovp)
{
iovp->iod_iovec_s -= IOVEC_NR;
iovp->iod_iovec_offset += IOVEC_NR * sizeof(iovec_t);
get_userdata_s(iovp->iod_proc_nr, iovp->iod_grant, iovp->iod_iovec_offset,
iovp->iod_iovec_s, iovp->iod_iovec);
return;
}
/*
** Name: int calc_iovec_size(iovec_dat_t *iovp)
** Function: Compute the size of a request.
*/
static int calc_iovec_size(iovec_dat_s_t * iovp)
{
int size, ix;
size = ix = 0;
do {
size += iovp->iod_iovec[ix].iov_size;
if (++ix >= IOVEC_NR) {
dp_next_iovec(iovp);
ix = 0;
}
/* Till all vectors added */
} while (ix < iovp->iod_iovec_s);
return size;
}
/*
** Name: void do_vwrite_s(message *mp)
** Function:
*/
static void do_vwrite_s(const message * mp)
{
int port, size;
dpeth_t *dep;
port = mp->DL_PORT;
if (port < 0 || port >= DE_PORT_NR) /* Check for illegal port number */
panic(PortErrMsg, EINVAL);
dep = &de_table[port];
dep->de_client = mp->DL_PROC;
if (dep->de_mode == DEM_ENABLED) {
if (dep->de_flags & DEF_SENDING) /* Is sending in progress? */
panic("send already in progress ");
dep->de_write_iovec.iod_proc_nr = mp->DL_PROC;
get_userdata_s(mp->DL_PROC, mp->DL_GRANT, 0,
mp->DL_COUNT, dep->de_write_iovec.iod_iovec);
dep->de_write_iovec.iod_iovec_s = mp->DL_COUNT;
dep->de_write_iovec.iod_grant = (cp_grant_id_t) mp->DL_GRANT;
dep->de_write_iovec.iod_iovec_offset = 0;
size = calc_iovec_size(&dep->de_write_iovec);
if (size < ETH_MIN_PACK_SIZE || size > ETH_MAX_PACK_SIZE)
panic(SizeErrMsg, size);
dep->de_flags |= DEF_SENDING;
(*dep->de_sendf) (dep, FALSE, size);
} else if (dep->de_mode == DEM_SINK)
dep->de_flags |= DEF_ACK_SEND;
reply(dep, OK, DL_TASK_REPLY);
return;
}
/*
** Name: void do_vread_s(message *mp, int vectored)
** Function:
*/
static void do_vread_s(const message * mp)
{
int port, size;
dpeth_t *dep;
port = mp->DL_PORT;
if (port < 0 || port >= DE_PORT_NR) /* Check for illegal port number */
panic(PortErrMsg, EINVAL);
dep = &de_table[port];
dep->de_client = mp->DL_PROC;
if (dep->de_mode == DEM_ENABLED) {
if (dep->de_flags & DEF_READING) /* Reading in progress */
panic("read already in progress");
dep->de_read_iovec.iod_proc_nr = mp->DL_PROC;
get_userdata_s(mp->DL_PROC, (cp_grant_id_t) mp->DL_GRANT, 0,
mp->DL_COUNT, dep->de_read_iovec.iod_iovec);
dep->de_read_iovec.iod_iovec_s = mp->DL_COUNT;
dep->de_read_iovec.iod_grant = (cp_grant_id_t) mp->DL_GRANT;
dep->de_read_iovec.iod_iovec_offset = 0;
size = calc_iovec_size(&dep->de_read_iovec);
if (size < ETH_MAX_PACK_SIZE) panic(SizeErrMsg, size);
dep->de_flags |= DEF_READING;
(*dep->de_recvf) (dep, FALSE, size);
#if 0
if ((dep->de_flags & (DEF_READING | DEF_STOPPED)) == (DEF_READING | DEF_STOPPED))
/* The chip is stopped, and all arrived packets delivered */
(*dep->de_resetf) (dep);
dep->de_flags &= NOT(DEF_STOPPED);
#endif
}
reply(dep, OK, DL_TASK_REPLY);
return;
}
/*
** Name: void do_getstat_s(message *mp)
** Function: Reports device statistics.
*/
static void do_getstat_s(const message * mp)
{
int port, rc;
dpeth_t *dep;
port = mp->DL_PORT;
if (port < 0 || port >= DE_PORT_NR) /* Check for illegal port number */
panic(PortErrMsg, EINVAL);
dep = &de_table[port];
dep->de_client = mp->DL_PROC;
if (dep->de_mode == DEM_ENABLED) (*dep->de_getstatsf) (dep);
if ((rc = sys_safecopyto(mp->DL_PROC, mp->DL_GRANT, 0,
(vir_bytes)&dep->de_stat,
(vir_bytes) sizeof(dep->de_stat), 0)) != OK)
panic(CopyErrMsg, rc);
reply(dep, OK, DL_STAT_REPLY);
return;
}
static void do_getname(mp)
message *mp;
{
int r;
strncpy(mp->DL_NAME, progname, sizeof(mp->DL_NAME));
mp->DL_NAME[sizeof(mp->DL_NAME)-1]= '\0';
mp->m_type= DL_NAME_REPLY;
r= send(mp->m_source, mp);
if (r != OK)
panic("do_getname: send failed: %d", r);
}
/*
** Name: void do_stop(message *mp)
** Function: Stops network interface.
*/
static void do_stop(const message * mp)
{
int port;
dpeth_t *dep;
port = mp->DL_PORT;
if (port < 0 || port >= DE_PORT_NR) /* Check for illegal port number */
panic(PortErrMsg, EINVAL);
dep = &de_table[port];
if (dep->de_mode == DEM_ENABLED && (dep->de_flags & DEF_ENABLED)) {
/* Stop device */
(dep->de_stopf) (dep);
dep->de_flags = DEF_EMPTY;
dep->de_mode = DEM_DISABLED;
}
return;
}
static void do_watchdog(const void *UNUSED(message))
{
DEBUG(printf("\t no reply"));
return;
}
PRIVATE void handle_hw_intr(void)
{
dpeth_t *dep;
for (dep = de_table; dep < &de_table[DE_PORT_NR]; dep += 1) {
/* If device is enabled and interrupt pending */
if (dep->de_mode == DEM_ENABLED) {
dep->de_int_pending = TRUE;
(*dep->de_interruptf) (dep);
if (dep->de_flags & (DEF_ACK_SEND | DEF_ACK_RECV))
reply(dep, !OK, DL_TASK_REPLY);
dep->de_int_pending = FALSE;
sys_irqenable(&dep->de_hook);
}
}
}
/* SEF functions and variables. */
FORWARD _PROTOTYPE( void sef_local_startup, (void) );
FORWARD _PROTOTYPE( int sef_cb_init_fresh, (int type, sef_init_info_t *info) );
FORWARD _PROTOTYPE( void sef_cb_signal_handler, (int signo) );
EXTERN char **env_argv;
/*
** Name: int dpeth_task(void)
** Function: Main entry for dp task
*/
PUBLIC int main(int argc, char **argv)
{
message m;
int ipc_status;
int rc;
/* SEF local startup. */
env_setargs(argc, argv);
sef_local_startup();
while (TRUE) {
if ((rc = netdriver_receive(ANY, &m, &ipc_status)) != OK){
panic(RecvErrMsg, rc);
}
DEBUG(printf("eth: got message %d, ", m.m_type));
if (is_ipc_notify(ipc_status)) {
switch(_ENDPOINT_P(m.m_source)) {
case CLOCK:
/* to be defined */
do_watchdog(&m);
break;
case HARDWARE:
/* Interrupt from device */
handle_hw_intr();
break;
case TTY_PROC_NR:
/* Function key pressed */
do_dump(&m);
break;
default:
/* Invalid message type */
panic(TypeErrMsg, m.m_type);
break;
}
/* message processed, get another one */
continue;
}
switch (m.m_type) {
case DL_WRITEV_S: /* Write message to device */
do_vwrite_s(&m);
break;
case DL_READV_S: /* Read message from device */
do_vread_s(&m);
break;
case DL_CONF: /* Initialize device */
do_init(&m);
break;
case DL_GETSTAT_S: /* Get device statistics */
do_getstat_s(&m);
break;
case DL_GETNAME:
do_getname(&m);
break;
case DL_STOP: /* Stop device */
do_stop(&m);
break;
default: /* Invalid message type */
panic(TypeErrMsg, m.m_type);
break;
}
}
return OK; /* Never reached, but keeps compiler happy */
}
/*===========================================================================*
* sef_local_startup *
*===========================================================================*/
PRIVATE void sef_local_startup()
{
/* Register init callbacks. */
sef_setcb_init_fresh(sef_cb_init_fresh);
sef_setcb_init_lu(sef_cb_init_fresh);
sef_setcb_init_restart(sef_cb_init_fresh);
/* Register live update callbacks. */
sef_setcb_lu_prepare(sef_cb_lu_prepare_always_ready);
sef_setcb_lu_state_isvalid(sef_cb_lu_state_isvalid_workfree);
/* Register signal callbacks. */
sef_setcb_signal_handler(sef_cb_signal_handler);
/* Let SEF perform startup. */
sef_startup();
}
/*===========================================================================*
* sef_cb_init_fresh *
*===========================================================================*/
PRIVATE int sef_cb_init_fresh(int type, sef_init_info_t *UNUSED(info))
{
/* Initialize the dpeth driver. */
int r, rc, fkeys, sfkeys;
(progname=strrchr(env_argv[0],'/')) ? progname++ : (progname=env_argv[0]);
/* Request function key for debug dumps */
fkeys = sfkeys = 0; bit_set(sfkeys, 8);
if ((fkey_map(&fkeys, &sfkeys)) != OK)
printf("%s: couldn't program Shift+F8 key (%d)\n", DevName, errno);
#ifdef ETH_IGN_PROTO
{
static u16_t eth_ign_proto = 0;
long val;
val = 0xFFFF;
env_parse("ETH_IGN_PROTO", "x", 0, &val, 0x0000L, 0xFFFFL);
eth_ign_proto = htons((u16_t) val);
}
#endif
/* Announce we are up! */
netdriver_announce();
return(OK);
}
/*===========================================================================*
* sef_cb_signal_handler *
*===========================================================================*/
PRIVATE void sef_cb_signal_handler(int signo)
{
int port;
message m;
/* Only check for termination signal, ignore anything else. */
if (signo != SIGTERM) return;
for (port = 0; port < DE_PORT_NR; port += 1) {
if (de_table[port].de_mode == DEM_ENABLED) {
m.m_type = DL_STOP;
m.DL_PORT = port;
do_stop(&m);
}
}
}
/** dp.c **/