aboutsummaryrefslogblamecommitdiffstats
path: root/erts/emulator/drivers/ose/ose_signal_drv.c
blob: 4929b538564803d2a095b9180f8c928e77d475b6 (plain) (tree)





























                                                                         
 
 
                     
                            
      










































































































































































































































































































































                                                                                             
 




























                                                           












                                                                    































































































                                                                            
                                                                         
                                                    


                                                                              
                                                                       
                                                    








































































































































































                                                                                                   
                                                                          





                                                          
                                                                      
                                                                      





































                                                                                   
                                                    


























































































                                                                                   
                                        




















                                                                     
                     



















                                                         
      













                                                          














                                                                 
                                             
  
 
/*
 * %CopyrightBegin%
 *
 * Copyright Ericsson AB 2013-2013. All Rights Reserved.
 *
 * The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved online at http://www.erlang.org/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * %CopyrightEnd%
 */
#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include "errno.h"
#include "stdio.h"
#include "string.h"
#include "stddef.h"

#include "sys.h"
#include "erl_driver.h"
#include "ose.h"


#ifdef HAVE_OSE_SPI_H
#include "ose_spi/ose_spi.h"
#endif

#define DEBUG_ATTACH   0
#define DEBUG_HUNT     0
#define DEBUG_SEND     0
#define DEBUG_LISTEN   0

#if 0
#define DEBUGP(FMT,...) printf(FMT, __VA_ARGS__)
#else
#define DEBUGP(FMT,...)
#endif

#if DEBUG_ATTACH
#define DEBUGP_ATTACH(...) DEBUGP( __VA_ARGS__)
#else
#define DEBUGP_ATTACH(...)
#endif

#if DEBUG_HUNT
#define DEBUGP_HUNT(...) DEBUGP( __VA_ARGS__)
#else
#define DEBUGP_HUNT(...)
#endif

#if DEBUG_LISTEN
#define DEBUGP_LISTEN(...) DEBUGP( __VA_ARGS__)
#else
#define DEBUGP_LISTEN(...)
#endif

#if DEBUG_SEND
#define DEBUGP_SEND(...) DEBUGP( __VA_ARGS__)
#else
#define DEBUGP_SEND(...)
#endif


#define DRIVER_NAME "ose_signal_drv"
#define GET_SPID        1
#define GET_NAME        2
#define HUNT          100
#define DEHUNT        101
#define ATTACH        102
#define DETACH        103
#define SEND          104
#define SEND_W_S      105
#define LISTEN        106
#define OPEN          200

#define REF_SEGMENT_SIZE 8

struct async {
  SIGSELECT signo;
  ErlDrvTermData port;
  ErlDrvTermData proc;
  PROCESS spid;
  PROCESS target;
  Uint32 ref;
};

/**
 * OSE signals
 **/
union SIGNAL {
  SIGSELECT signo;
  struct async async;
};

/**
 * The driver's context
 **/
typedef struct _driver_context {
  ErlDrvPort port;
  PROCESS spid;
  ErlDrvEvent perm_events[2];
  ErlDrvEvent *events;
  Uint32 event_cnt;
  Uint32 ref;
  Uint32 *outstanding_refs;
  Uint32 outstanding_refs_max;
  Uint32 outstanding_refs_cnt;
} driver_context_t;

/**
 * Global variables
 **/
static ErlDrvTermData a_ok;
static ErlDrvTermData a_error;
static ErlDrvTermData a_enomem;
static ErlDrvTermData a_enoent;
static ErlDrvTermData a_badarg;
static ErlDrvTermData a_mailbox_up;
static ErlDrvTermData a_mailbox_down;
static ErlDrvTermData a_ose_drv_reply;
static ErlDrvTermData a_message;
static PROCESS proxy_proc;


/**
 * Serialize/unserialize unsigned 32-bit values
 **/
static char *put_u32(unsigned int value, char *ptr) {
  *ptr++ = (value & 0xff000000) >> 24;
  *ptr++ = (value & 0x00ff0000) >> 16;
  *ptr++ = (value & 0x0000ff00) >> 8;
  *ptr++ = (value & 0xff);

  return ptr;
}

static unsigned int get_u32(char *ptr) {
  unsigned int result = 0;
  result += (ptr[0] & 0xff) << 24;
  result += (ptr[1] & 0xff) << 16;
  result += (ptr[2] & 0xff) << 8;
  result += (ptr[3] & 0xff);

  return result;
}


/* Stolen from efile_drv.c */

/* char EV_CHAR_P(ErlIOVec *ev, int p, int q) */
#define EV_CHAR_P(ev, p, q)                   \
    (((char *)(ev)->iov[(q)].iov_base) + (p))

/* int EV_GET_CHAR(ErlIOVec *ev, char *p, int *pp, int *qp) */
#define EV_GET_CHAR(ev, p, pp, qp) ev_get_char(ev, p ,pp, qp)
static int
ev_get_char(ErlIOVec *ev, char *p, int *pp, int *qp) {
  if (*(pp)+1 <= (ev)->iov[*(qp)].iov_len) {
    *(p) = *EV_CHAR_P(ev, *(pp), *(qp));
    if (*(pp)+1 < (ev)->iov[*(qp)].iov_len)
      *(pp) = *(pp)+1;
    else {
      (*(qp))++;
      *pp = 0;
    }
    return !0;
  }
  return 0;
}

/* Uint32 EV_UINT32(ErlIOVec *ev, int p, int q)*/
#define EV_UINT32(ev, p, q) \
    ((Uint32) *(((unsigned char *)(ev)->iov[(q)].iov_base) + (p)))

/* int EV_GET_UINT32(ErlIOVec *ev, Uint32 *p, int *pp, int *qp) */
#define EV_GET_UINT32(ev, p, pp, qp) ev_get_uint32(ev,(Uint32*)(p),pp,qp)
static int
ev_get_uint32(ErlIOVec *ev, Uint32 *p, int *pp, int *qp) {
  if (*(pp)+4 <= (ev)->iov[*(qp)].iov_len) {
    *(p) = (EV_UINT32(ev, *(pp),   *(qp)) << 24)
      | (EV_UINT32(ev, *(pp)+1, *(qp)) << 16)
      | (EV_UINT32(ev, *(pp)+2, *(qp)) << 8)
      | (EV_UINT32(ev, *(pp)+3, *(qp)));
    if (*(pp)+4 < (ev)->iov[*(qp)].iov_len)
      *(pp) = *(pp)+4;
    else {
      (*(qp))++;
      *pp = 0;
    }
    return !0;
  }
  return 0;
}

/**
 * Convinience macros
 **/
#define send_response(port,output) erl_drv_send_term(driver_mk_port(port),\
    driver_caller(port), output, sizeof(output) / sizeof(output[0]));

void iov_memcpy(void *dest,ErlIOVec *ev,int ind,int off);
void iov_memcpy(void *dest,ErlIOVec *ev,int ind,int off) {
  int i;
  memcpy(dest,ev->iov[ind].iov_base+off,ev->iov[ind].iov_len-off);
  for (i = ind+1; i < ev->vsize; i++)
    memcpy(dest,ev->iov[i].iov_base,ev->iov[i].iov_len);
}

/**
 * Reference handling
 **/

static int add_reference(driver_context_t *ctxt, Uint32 ref) {

  /*
   * Premature optimizations may be evil, but they sure are fun.
   */

  if (ctxt->outstanding_refs == NULL) {
    /* First ref to be ignored */
    ctxt->outstanding_refs = driver_alloc(REF_SEGMENT_SIZE*sizeof(Uint32));
    if (!ctxt->outstanding_refs)
      return 1;

    memset(ctxt->outstanding_refs,0,REF_SEGMENT_SIZE*sizeof(Uint32));
    ctxt->outstanding_refs_max += REF_SEGMENT_SIZE;
    ctxt->outstanding_refs[ctxt->outstanding_refs_cnt++] = ref;
  } else if (ctxt->outstanding_refs_cnt == ctxt->outstanding_refs_max) {
    /* Expand ref array */
    Uint32 *new_array;
    ctxt->outstanding_refs_max += REF_SEGMENT_SIZE;
    new_array = driver_realloc(ctxt->outstanding_refs,
			       ctxt->outstanding_refs_max*sizeof(Uint32));

    if (!new_array) {
      ctxt->outstanding_refs_max -= REF_SEGMENT_SIZE;
      return 1;
    }

    ctxt->outstanding_refs = new_array;

    memset(ctxt->outstanding_refs+ctxt->outstanding_refs_cnt,0,
	   REF_SEGMENT_SIZE*sizeof(Uint32));
    ctxt->outstanding_refs[ctxt->outstanding_refs_cnt++] = ref;

  } else {
    /* Find an empty slot:
     *   First we try current index,
     *   then we scan for a slot.
     */
    if (!ctxt->outstanding_refs[ctxt->outstanding_refs_cnt]) {
      ctxt->outstanding_refs[ctxt->outstanding_refs_cnt++] = ref;
    } else {
      int i;
      ASSERT(ctxt->outstanding_refs_cnt < ctxt->outstanding_refs_max);
      for (i = 0; i < ctxt->outstanding_refs_max; i++)
	if (!ctxt->outstanding_refs[i])
	  break;
      ASSERT(ctxt->outstanding_refs[i] == 0);
      ctxt->outstanding_refs[i] = ref;
      ctxt->outstanding_refs_cnt++;
    }
  }
  return 0;
}

/* Return 0 if removed, 1 if does not exist, */
static int remove_reference(driver_context_t *ctxt, Uint32 ref) {
  int i,j;

  if (ctxt->outstanding_refs_max == 0 && ctxt->outstanding_refs_cnt == 0) {
    ASSERT(ctxt->outstanding_refs == NULL);
    return 1;
  }

  for (i = 0; i < ctxt->outstanding_refs_max; i++) {
    if (ctxt->outstanding_refs[i] == ref) {
      ctxt->outstanding_refs[i] = 0;
      ctxt->outstanding_refs_cnt--;
      i = -1;
      break;
    }
  }

  if (i != -1)
    return 1;

  if (ctxt->outstanding_refs_cnt == 0) {
    driver_free(ctxt->outstanding_refs);
    ctxt->outstanding_refs = NULL;
    ctxt->outstanding_refs_max = 0;
  } else if (ctxt->outstanding_refs_cnt == (ctxt->outstanding_refs_max - REF_SEGMENT_SIZE)) {
    Uint32 *new_array;
    for (i = 0, j = 0; i < ctxt->outstanding_refs_cnt; i++) {
      if (ctxt->outstanding_refs[i] == 0) {
	for (j = i+1; j < ctxt->outstanding_refs_max; j++)
	  if (ctxt->outstanding_refs[j]) {
	    ctxt->outstanding_refs[i] = ctxt->outstanding_refs[j];
	    ctxt->outstanding_refs[j] = 0;
	    break;
	  }
      }
    }
    ctxt->outstanding_refs_max -= REF_SEGMENT_SIZE;
    new_array = driver_realloc(ctxt->outstanding_refs,
			       ctxt->outstanding_refs_max*sizeof(Uint32));
    if (!new_array) {
      ctxt->outstanding_refs_max += REF_SEGMENT_SIZE;
      return 2;
    }

    ctxt->outstanding_refs = new_array;

  }

  return 0;
}

/**
 * The OSE proxy process. This only handles ERTS_SIGNAL_OSE_DRV_ATTACH.
 * The process is needed because signals triggered by attach ignore
 * redir tables.
 *
 * We have one global proxy process to save memory. An attempt to make each
 * port phantom into a proxy was made, but that used way to much memory.
 */
static OS_PROCESS(driver_proxy_process) {
  SIGSELECT sigs[] = {1,ERTS_SIGNAL_OSE_DRV_ATTACH};
  PROCESS master = 0;

  while (1) {
    union SIGNAL *sig = receive(sigs);

    if (sig->signo == ERTS_SIGNAL_OSE_DRV_ATTACH) {

      /* The first message is used to determine who to send messages to. */
      if (master == 0)
	master = sender(&sig);

      if (sig->async.target == 0) {
	PROCESS from = sender(&sig);
	restore(sig);
	DEBUGP_ATTACH("0x%x: got attach 0x%x, sending to 0x%x\n",
		      current_process(),from,master);
	sig->async.target = from;
	send(&sig,master);
      } else {
	PROCESS target = sig->async.target;
	restore(sig);
	sig->async.target = 0;
	DEBUGP_ATTACH("0x%x: doing attach on 0x%x\n",current_process(),target);
	attach(&sig,target);
      }
    }
  }
}


/**
 * Init routine for the driver
 **/
static int drv_init(void) {

  a_ok = driver_mk_atom("ok");
  a_error = driver_mk_atom("error");
  a_enomem = driver_mk_atom("enomem");
  a_enoent = driver_mk_atom("enoent");
  a_badarg = driver_mk_atom("badarg");
  a_mailbox_up = driver_mk_atom("mailbox_up");
  a_mailbox_down = driver_mk_atom("mailbox_down");
  a_ose_drv_reply = driver_mk_atom("ose_drv_reply");
  a_message = driver_mk_atom("message");

  proxy_proc = create_process(get_ptype(current_process()),
			      "ose_signal_driver_proxy",
			      driver_proxy_process, 10000,
			      get_pri(current_process()),
			      0, 0, NULL, 0, 0);

#ifdef DEBUG
  efs_clone(proxy_proc);
#endif
  start(proxy_proc);

  return 0;
}

/* Signal resolution callback */
static ErlDrvOseEventId resolve_signal(union SIGNAL* osig) {
  union SIGNAL *sig = osig;
  if (sig->signo == ERTS_SIGNAL_OSE_DRV_HUNT ||
      sig->signo == ERTS_SIGNAL_OSE_DRV_ATTACH) {
    return sig->async.spid;
  }
  DEBUGP("%p: Got signal %d sent to %p from 0x%p\n",
	 current_process(),sig->signo,addressee(&sig),sender(&sig));
  return addressee(&sig);
}


/**
 * Start routine for the driver
 **/
static ErlDrvData drv_start(ErlDrvPort port, char *command)
{
  driver_context_t *ctxt = driver_alloc(sizeof(driver_context_t));

  ctxt->perm_events[0] = NULL;
  ctxt->perm_events[1] = NULL;

  ctxt->spid = 0;
  ctxt->port = port;
  ctxt->event_cnt = 0;
  ctxt->events = NULL;
  ctxt->ref = 0;
  ctxt->outstanding_refs = NULL;
  ctxt->outstanding_refs_max = 0;
  ctxt->outstanding_refs_cnt = 0;


  /* Set the communication protocol to Erlang to be binary */
  set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);

  /* Everything ok */
  return (ErlDrvData)ctxt;
}

/**
 * Stop routine for the driver
 **/
static void drv_stop(ErlDrvData driver_data)
{
  driver_context_t *ctxt = (driver_context_t *)driver_data;
  int i;

  /* HUNT + ATTACH */
  if (ctxt->perm_events[0])
    driver_select(ctxt->port, ctxt->perm_events[0],
		  ERL_DRV_USE|ERL_DRV_READ, 0);
  if (ctxt->perm_events[1])
    driver_select(ctxt->port, ctxt->perm_events[1],
		  ERL_DRV_USE|ERL_DRV_READ, 0);

  for (i = 0; i < ctxt->event_cnt; i++) {
    driver_select(ctxt->port, ctxt->events[i], ERL_DRV_USE|ERL_DRV_READ, 0);
  }

  if (ctxt->spid != 0)
    kill_proc(ctxt->spid);
  DEBUGP("0x%x: stopped\n",ctxt->spid);
  if (ctxt->events)
    driver_free(ctxt->events);
  if (ctxt->outstanding_refs)
    driver_free(ctxt->outstanding_refs);

  driver_free(ctxt);
}

/**
 * Output from Erlang
 **/
static void outputv(ErlDrvData driver_data, ErlIOVec *ev)
{
  driver_context_t *ctxt = (driver_context_t *)driver_data;
  int p = 0, q = 1;
  char cmd;

  if (! EV_GET_CHAR(ev,&cmd,&p,&q)) {
    ErlDrvTermData output[] = {
      ERL_DRV_ATOM, a_ose_drv_reply,
      ERL_DRV_PORT, driver_mk_port(ctxt->port),
      ERL_DRV_ATOM, a_badarg,
      ERL_DRV_TUPLE, 3};
    send_response(ctxt->port, output);
    return;
  }

  /* Command is in the buffer's first byte */
  switch(cmd) {

  case OPEN: {
    char *name = driver_alloc(ev->size - 1+1);
    struct OS_redir_entry redir[2];

    redir[0].sig = 1;
    redir[0].pid = current_process();

    iov_memcpy(name,ev,q,p);
    name[ev->size-1] = '\0';

    ctxt->spid = create_process(OS_PHANTOM, name, NULL, 0,
				0, 0, 0, redir, 0, 0);

    DEBUGP("0x%x: open\n",ctxt->spid);

    ctxt->perm_events[1] =
      erl_drv_ose_event_alloc(ERTS_SIGNAL_OSE_DRV_ATTACH,(int)ctxt->spid,
			      resolve_signal, NULL);
    driver_select(ctxt->port,ctxt->perm_events[1],ERL_DRV_READ|ERL_DRV_USE,1);

    ctxt->perm_events[0] =
      erl_drv_ose_event_alloc(ERTS_SIGNAL_OSE_DRV_HUNT,(int)ctxt->spid,
			      resolve_signal, NULL);
    driver_select(ctxt->port,ctxt->perm_events[0],ERL_DRV_READ|ERL_DRV_USE,1);

    start(ctxt->spid);

    {
      ErlDrvTermData output[] = {
	ERL_DRV_ATOM, a_ose_drv_reply,
	ERL_DRV_PORT, driver_mk_port(ctxt->port),
	ERL_DRV_ATOM, a_ok,
	ERL_DRV_TUPLE, 3};

      send_response(ctxt->port, output);
    }

    break;

  }

  case ATTACH:
  case HUNT:
    {
      union SIGNAL *sig = alloc(sizeof(union SIGNAL),
				cmd == HUNT ? ERTS_SIGNAL_OSE_DRV_HUNT:ERTS_SIGNAL_OSE_DRV_ATTACH);

      sig->async.port = driver_mk_port(ctxt->port);
      sig->async.proc = driver_caller(ctxt->port);
      sig->async.spid = ctxt->spid;
      sig->async.ref = ++ctxt->ref;

      if (add_reference(ctxt,ctxt->ref)) {
	ErlDrvTermData output[] = {
	  ERL_DRV_ATOM, a_ose_drv_reply,
	  ERL_DRV_PORT, driver_mk_port(ctxt->port),
	  ERL_DRV_ATOM, a_enomem,
	  ERL_DRV_TUPLE, 3};
	send_response(ctxt->port, output);
	free_buf(&sig);
      } else {
	ErlDrvTermData output[] = {
	  ERL_DRV_ATOM, a_ose_drv_reply,
	  ERL_DRV_PORT, driver_mk_port(ctxt->port),
	  ERL_DRV_PORT, driver_mk_port(ctxt->port),
	  ERL_DRV_INT, (ErlDrvUInt)ctxt->ref,
	  ERL_DRV_TUPLE, 2,
	  ERL_DRV_TUPLE, 3};
	send_response(ctxt->port, output);

	if (cmd == HUNT) {
	  char *huntname = driver_alloc(sizeof(char)*((ev->size-1)+1));

	  iov_memcpy(huntname,ev,q,p);
	  huntname[ev->size-1] = '\0';

	  DEBUGP_HUNT("0x%x: hunt %s -> %u (%u,%u)\n",
		      ctxt->spid,huntname,ctxt->ref,
		      ctxt->outstanding_refs_cnt,
		      ctxt->outstanding_refs_max);

	  hunt(huntname, 0, NULL, &sig);

	  driver_free(huntname);
	} else {
	  EV_GET_UINT32(ev,&sig->async.target,&p,&q);
	  DEBUGP_ATTACH("0x%x: attach %u -> %u (%u,%u)\n",
			ctxt->spid,sig->async.target,
			ctxt->ref,
			ctxt->outstanding_refs_cnt,
			ctxt->outstanding_refs_max);

	  send(&sig,proxy_proc);
	}

      }

      break;
    }

  case DETACH:
  case DEHUNT:
    {

        Uint32 ref;

	EV_GET_UINT32(ev,&ref,&p,&q);
	if (cmd == DETACH) {
	  DEBUGP_ATTACH("0x%x: detach %u (%u,%u)\n",ctxt->spid,ref,
			ctxt->outstanding_refs_cnt,
			ctxt->outstanding_refs_max);
	} else {
	  DEBUGP_HUNT("0x%x: dehunt %u (%u,%u)\n",ctxt->spid,ref,
		      ctxt->outstanding_refs_cnt,
		      ctxt->outstanding_refs_max);
	}

	if (remove_reference(ctxt,ref)) {
	  ErlDrvTermData output[] = {
	    ERL_DRV_ATOM, a_ose_drv_reply,
	    ERL_DRV_PORT, driver_mk_port(ctxt->port),
	    ERL_DRV_ATOM, a_error,
	    ERL_DRV_ATOM, a_enoent,
	    ERL_DRV_TUPLE, 2,
	    ERL_DRV_TUPLE, 3};

	  send_response(ctxt->port, output);
	} else {
	  ErlDrvTermData output[] = {
	    ERL_DRV_ATOM, a_ose_drv_reply,
	    ERL_DRV_PORT, driver_mk_port(ctxt->port),
	    ERL_DRV_ATOM, a_ok,
	    ERL_DRV_TUPLE, 3};

	  send_response(ctxt->port, output);
	}

      break;
    }

  case SEND:
  case SEND_W_S:
    {
      PROCESS spid;
      PROCESS sender;
      SIGSELECT signo;
      OSBUFSIZE size = ev->size-9;
      union SIGNAL *sig;

      EV_GET_UINT32(ev,&spid,&p,&q);

      if (cmd == SEND_W_S) {
	EV_GET_UINT32(ev,&sender,&p,&q);
	size -= 4;
      } else {
	sender = ctxt->spid;
      }

      EV_GET_UINT32(ev,&signo,&p,&q);

      sig = alloc(size + sizeof(SIGSELECT),signo);

      if (cmd == SEND_W_S) {
	DEBUGP_SEND("0x%x: send_w_s(%u,%u,%u)\n",ctxt->spid,spid,signo,sender);
      } else {
	DEBUGP_SEND("0x%x: send(%u,%u)\n",ctxt->spid,spid,signo);
      }

      iov_memcpy(((char *)&sig->signo) + sizeof(SIGSELECT),ev,q,p);

      send_w_s(&sig, sender, spid);

      break;
    }

    case LISTEN:
    {
      int i,j,event_cnt = (ev->size - 1)/4;
      ErlDrvEvent *events = NULL;
      SIGSELECT signo,tmp_signo;

      if (event_cnt == 0) {
	for (i = 0; i < ctxt->event_cnt; i++)
	  driver_select(ctxt->port,ctxt->events[i],ERL_DRV_READ|ERL_DRV_USE,0);
	if (ctxt->events)
	  driver_free(ctxt->events);
      } else {
	events = driver_alloc(sizeof(ErlDrvEvent)*event_cnt);
	EV_GET_UINT32(ev,&signo,&p,&q);
	for (i = 0, j = 0; i < event_cnt || j < ctxt->event_cnt; ) {

	  if (ctxt->events)
	    erl_drv_ose_event_fetch(ctxt->events[j],&tmp_signo,NULL,NULL);

	  if (signo == tmp_signo) {
	    events[i++] = ctxt->events[j++];
	    EV_GET_UINT32(ev,&signo,&p,&q);
	  } else if (signo < tmp_signo || !ctxt->events) {
	    /* New signal to select on */
	    events[i] = erl_drv_ose_event_alloc(signo,(int)ctxt->spid,
						resolve_signal, NULL);
	    driver_select(ctxt->port,events[i++],ERL_DRV_READ|ERL_DRV_USE,1);
	    EV_GET_UINT32(ev,&signo,&p,&q);
	  } else {
	    /* Remove old signal to select on */
	    driver_select(ctxt->port,ctxt->events[j++],ERL_DRV_READ|ERL_DRV_USE,0);
	  }
	}
	if (ctxt->events)
	  driver_free(ctxt->events);
      }
      ctxt->events = events;
      ctxt->event_cnt = event_cnt;

      {
	ErlDrvTermData output[] = {
	  ERL_DRV_ATOM, a_ose_drv_reply,
	  ERL_DRV_PORT, driver_mk_port(ctxt->port),
	  ERL_DRV_ATOM, a_ok,
	  ERL_DRV_TUPLE, 3};
	send_response(ctxt->port, output);
      }
      break;
    }

    default:
    {
      DEBUGP("Warning: 'ose_signal_drv' unknown command '%d'\n", cmd);
      break;
    }
  }
}

/**
 * Handler for when OSE signal arrives
 **/
static void ready_input(ErlDrvData driver_data, ErlDrvEvent event)
{
  driver_context_t *ctxt = (driver_context_t *)driver_data;
  union SIGNAL *sig = erl_drv_ose_get_signal(event);

  while (sig != NULL) {

    switch(sig->signo)
      {
	/* Remote process is available */
      case ERTS_SIGNAL_OSE_DRV_HUNT:
	{
	  const PROCESS spid = sender(&sig);

	  if (remove_reference(ctxt,sig->async.ref)) {
	    DEBUGP_HUNT("0x%x: Got hunt from 0x%x -> %u (CANCELLED) (%u,%u)\n",
			ctxt->spid,spid,sig->async.ref,
			ctxt->outstanding_refs_cnt,
			ctxt->outstanding_refs_max);
	    /* Already removed by dehunt */
	  } else {
	    ErlDrvTermData reply[] = {
	      ERL_DRV_ATOM, a_mailbox_up,
	      ERL_DRV_PORT, sig->async.port,
	      ERL_DRV_PORT, sig->async.port,
	      ERL_DRV_UINT, (ErlDrvUInt)sig->async.ref,
	      ERL_DRV_TUPLE, 2,
	      ERL_DRV_UINT, (ErlDrvUInt)spid,
	      ERL_DRV_TUPLE, 4};
	    DEBUGP_HUNT("0x%x: Got hunt from 0x%x -> %u (%u,%u)\n",
			ctxt->spid,spid,sig->async.ref,
			ctxt->outstanding_refs_cnt,
			ctxt->outstanding_refs_max);
	    erl_drv_send_term(sig->async.port, sig->async.proc, reply,
			      sizeof(reply) / sizeof(reply[0]));
	  }
	  break;
	}

	/* Remote process is down */
      case ERTS_SIGNAL_OSE_DRV_ATTACH:
	{
	  PROCESS spid = sig->async.target;

	  if (remove_reference(ctxt,sig->async.ref)) {
	    DEBUGP_ATTACH("0x%x: Got attach from 0x%x -> %u (CANCELLED) (%u,%u)\n",
			  ctxt->spid,spid,sig->async.ref,
			  ctxt->outstanding_refs_cnt,
			  ctxt->outstanding_refs_max);
	    /* Already removed by detach */
	  } else {
	    ErlDrvTermData reply[] = {
	      ERL_DRV_ATOM, a_mailbox_down,
	      ERL_DRV_PORT, sig->async.port,
	      ERL_DRV_PORT, sig->async.port,
	      ERL_DRV_UINT, sig->async.ref,
	      ERL_DRV_TUPLE, 2,
	      ERL_DRV_UINT, (ErlDrvUInt)spid,
	      ERL_DRV_TUPLE, 4};
	     DEBUGP_ATTACH("0x%x: Got attach from 0x%x -> %u (%u,%u)\n",
			   ctxt->spid,spid,sig->async.ref,
			   ctxt->outstanding_refs_cnt,
			   ctxt->outstanding_refs_max);
	    erl_drv_send_term(sig->async.port, sig->async.proc, reply,
			      sizeof(reply) / sizeof(reply[0]));
	  }
	  break;
	}

	/* Received user defined signal */
      default:
	{
	  const PROCESS spid = sender(&sig);
	  const OSBUFSIZE size = sigsize(&sig) - sizeof(SIGSELECT);
	  const char *sig_data = ((char *)&sig->signo) + sizeof(SIGSELECT);

	  ErlDrvTermData reply[] = {
	    ERL_DRV_ATOM, a_message,
	    ERL_DRV_PORT, driver_mk_port(ctxt->port),
	    ERL_DRV_UINT, (ErlDrvUInt)spid,
	    ERL_DRV_UINT, (ErlDrvUInt)ctxt->spid,
	    ERL_DRV_UINT, (ErlDrvUInt)sig->signo,
	    ERL_DRV_BUF2BINARY, (ErlDrvTermData)sig_data, (ErlDrvUInt)size,
	    ERL_DRV_TUPLE, 4,
	    ERL_DRV_TUPLE, 3};

	  DEBUGP_SEND("0x%x: Got 0x%u\r\n", spid, sig->signo);

	  erl_drv_output_term(driver_mk_port(ctxt->port), reply,
			      sizeof(reply) / sizeof(reply[0]));
	  break;
	}
      }

    free_buf(&sig);
    sig = erl_drv_ose_get_signal(event);
  }
}

/**
 * Handler for 'port_control'
 **/
static ErlDrvSSizeT control(ErlDrvData driver_data, unsigned int cmd,
                            char *buf, ErlDrvSizeT len,
                            char **rbuf, ErlDrvSizeT rlen)
{
  driver_context_t *ctxt = (driver_context_t *)driver_data;

  switch(cmd)
  {
    case GET_SPID:
    {
      const PROCESS spid = ctxt->spid;
      put_u32(spid, *rbuf);
      return sizeof(PROCESS);
    }

#ifdef HAVE_OSE_SPI_H
    case GET_NAME:
    {
      const PROCESS spid = get_u32(buf);
      char *name = (char*)get_pid_info(spid,OSE_PI_NAME);
      int n;
      if (!name) {
	*rbuf = NULL;
	return 0;
      }

      if (rlen < (n = strlen(name))) {
	ErlDrvBinary *bin = driver_alloc_binary(n);
	strncpy(bin->orig_bytes,name,n);
	*rbuf = (char*)bin;
      } else
	strncpy(*rbuf,name,n);
      free_buf((union SIGNAL**)&name);

      return n;
    }
#endif
    default:
    {
      /* Unknown command */
      return (ErlDrvSSizeT)ERL_DRV_ERROR_GENERAL;
      break;
    }
  }
}

static void stop_select(ErlDrvEvent event, void *reserved)
{
  erl_drv_ose_event_free(event);
}

/**
 * Setup the driver entry for the Erlang runtime
 **/
ErlDrvEntry ose_signal_driver_entry = {
  .init                         = drv_init,
  .start                        = drv_start,
  .stop                         = drv_stop,
  .outputv                      = outputv,
  .ready_input                  = ready_input,
  .driver_name                  = DRIVER_NAME,
  .control                      = control,
  .extended_marker              = ERL_DRV_EXTENDED_MARKER,
  .major_version                = ERL_DRV_EXTENDED_MAJOR_VERSION,
  .minor_version                = ERL_DRV_EXTENDED_MINOR_VERSION,
  .driver_flags                 = ERL_DRV_FLAG_USE_PORT_LOCKING,
  .stop_select                  = stop_select
};