_WRITING SERIAL DRIVERS FOR UNIX_ by Bill Wells Listing One STATIC void sio_change_line_state(SIO_CTL *ctl) { LINE_STATE new_state; LINE_STATE old_state; sio_record_call(EV_CHANGE_LINE_STATE, ctl->sc_unit, 0); /* What should the new state be? Return if no change. */ if (ctl->sc_shutdown != CL_NONE) { new_state = ST_SHUTDOWN; } else if (ctl->sc_actin || ctl->sc_actout) { new_state = ST_ACTIVE; } else if (ctl->sc_winc || ctl->sc_woutc) { new_state = ST_WOPEN; } else { new_state = ST_INACT; } old_state = ctl->sc_lstate; if (old_state == new_state) { sio_set_modem_control(ctl); sio_record_return(EV_CHANGE_LINE_STATE, 0, 0); return; } sio_record_event(EV_LSTATE, new_state, 0); ctl->sc_lstate = new_state; if (new_state == ST_ACTIVE) { sio_flush_input(ctl); } if (new_state == ST_INACT) { ctl->sc_rtsline = ctl->sc_dtrline = 0; ctl->sc_carrier = 0; } sio_set_interrupt_state(ctl); if (old_state == ST_INACT) { ctl->sc_rtsline = ctl->sc_dtrline = 1; } sio_set_modem_control(ctl); sio_wake_open(ctl); sio_record_return(EV_CHANGE_LINE_STATE, 1, 0); } Listing Two int sioopen(dev_t dev, int flag, int mode, PROC *p) { SIO_CTL *ctl; bool_t callout; dev_t unit; spl_t x; TTY *tp; const char *reason; error_t error; SIO_IF_DEBUG(static ucount_t onum;) sio_record_call(EV_SIOOPEN, minor(dev), flag); /* Extract the unit number and callout flag. */ SIO_IF_DEBUG(++onum;) unit = UNIT(dev); if ((u_int)unit >= NSIO || !(ctl = sio_ptrs[unit])) { sio_record_return(EV_SIOOPEN, 0, ENXIO); return (ENXIO); } callout = CALLOUT(dev); dev = makedev(major(dev), UNIT(dev)); tp = ctl->sc_tty; /* Record that we're waiting for an open. */ if (callout) { ++ctl->sc_woutc; } else { ++ctl->sc_winc; } sio_set_wopen(ctl); error = 0; x = spltty(); while (1) { /* Get the device set up as necessary. */ sio_change_line_state(ctl); /* If the line is set to exclude opens, and if the line is actually open, forbid anyone but root from opening it. */ if ((tp->t_state & TS_XCLUDE) && (ctl->sc_actout || ctl->sc_actin) && p->p_ucred->cr_uid != 0) { error = EBUSY; break; /* Shutdown temporarily prevents all opens. */ } else if (ctl->sc_shutdown != CL_NONE) { reason = "sioocls"; /* A dialout open succeeds unless there is an active dialin open, in which case it fails. */ } else if (callout) { if (!ctl->sc_actin) { break; } if (!(tp->t_cflag & CLOCAL)) { error = EBUSY; break; } reason = "sioinw"; /* A dialin open will not succeed while there are active or pending dialout opens. It also requires a carrier or clocal. */ } else { if (ctl->sc_actout || ctl->sc_woutc) { reason = "sioout"; } else if (!(tp->t_cflag & CLOCAL) && !(tp->t_state & TS_CARR_ON)) { reason = "siocar"; } else { break; } } /* If we're here, either the line was in shutdown or a dialin open is going to wait. If this is a nonblocking open, return. Otherwise, sleep. */ if (flag & O_NONBLOCK) { error = EWOULDBLOCK; break; } sio_record_event(EV_SLEEP, onum, 0); error = tsleep((caddr_t)ctl, TTIPRI | PCATCH, reason, 0); sio_record_event(EV_WAKE, onum, error); if (error != 0) { break; } } /* The open has succeeded. We're no longer waiting for open.*/ if (callout) { --ctl->sc_woutc; } else { --ctl->sc_winc; } sio_set_wopen(ctl); /* If the open errored, reset the device and return the error. */ if (error != 0) { sio_change_line_state(ctl); splx(x); sio_record_return(EV_SIOOPEN, 1, error); return (error); } /* Next, set up the tty structure. */ tp->t_oproc = siostart; tp->t_param = sioparam; if (!(tp->t_state & TS_ISOPEN)) { tp->t_dev = dev; ttychars(tp); if (!tp->t_ispeed) { tp->t_iflag = 0; tp->t_oflag = 0; tp->t_cflag = CREAD | CS8 | HUPCL; tp->t_lflag = 0; tp->t_ispeed = tp->t_ospeed = TTYDEF_SPEED; } ttsetwater(tp); (void)sioparam(tp, &tp->t_termios); } /* Do the line discipline open. This marks the line open. */ error = (*linesw[tp->t_line].l_open)(dev, tp, 0); if (error != 0) { sio_change_line_state(ctl); splx(x); sio_record_return(EV_SIOOPEN, 2, error); return (error); } /* The line is now open. Let it rip. */ if (callout) { ctl->sc_actout = 1; /* Dialout devices start by pretending they have carrier. */ tp->t_state |= TS_CARR_ON; } else { ctl->sc_actin = 1; } sio_set_wopen(ctl); sio_change_line_state(ctl); splx(x); sio_record_return(EV_SIOOPEN, 3, error); return (error); } Listing Three #include #if !defined(RB_PREFIX) #define RB_PREFIX rb_ #define RB_TYPE char #define RB_CONTROL RBUF #define RB_QUAL static #endif #define RB_GLUE1(x,y) x ## y #define RB_GLUE(x,y) RB_GLUE1(x,y) #define RB_NAME(x) RB_GLUE(RB_PREFIX, x) #if !defined(RB_SET) #define RB_SET(buf,rh,wh) (\ (buf)->rb_rhold = (buf)->rb_start + (rh),\ (buf)->rb_whold = (buf)->rb_start + (wh)) #define RB_GET(buf,rh,wh) (\ (rh) = (buf)->rb_rhold - (buf)->rb_start,\ (wh) = (buf)->rb_whold - (buf)->rb_start) #endif #if !defined(RB_OVERHEAD) #define RB_OVERHEAD (1) #endif typedef struct { RB_TYPE *volatile rb_rhold; RB_TYPE *volatile rb_whold; RB_TYPE *rb_start; RB_TYPE *rb_end; size_t rb_size; } RB_CONTROL; /* This initializes a ring buffer. */ RB_QUAL void RB_NAME(init)(RB_CONTROL *p, RB_TYPE *d, size_t n) { p->rb_start = d; p->rb_end = d + n; p->rb_size = n; p->rb_whold = p->rb_start; p->rb_rhold = p->rb_end - 1; } /* Returns the size of the ring buffer. Note that this is the maximum number of elements that may be placed in it, not the size of allocated area. */ RB_QUAL size_t RB_NAME(size)(const RB_CONTROL *p) { return (p->rb_size - 1); } /* This writes one datum to the ring buffer. The return value is the number of items written, 0 or 1. */ RB_QUAL size_t RB_NAME(putc)(RB_CONTROL *p, const RB_TYPE *d) { RB_TYPE *wp; wp = p->rb_whold; if (wp == p->rb_rhold) { return (0); } *wp++ = *d; if (wp == p->rb_end) { wp = p->rb_start; } p->rb_whold = wp; return (1); } /* This writes an arbitrary number of elements to the ring buffer. The return value is the number of items written. */ RB_QUAL size_t RB_NAME(puts)(RB_CONTROL *p, const RB_TYPE *d, size_t n) { RB_TYPE *rh = p->rb_rhold; RB_TYPE *wp; size_t c; size_t r; /* If the data in the buffer is wrapped, the hole into which data may be placed is not wrapped. This makes a wrapped buffer be the easy case. */ wp = p->rb_whold; if (wp < rh) { c = rh - wp; if (n < c) { c = n; } if (!c) { return (0); } r = c; do { *wp++ = *d++; } while (--c); p->rb_whold = wp; return (r); } /* This next case handles an unwrapped buffer where the data fits before the end of the buffer. */ c = p->rb_end - wp; if (c >= n) { c = n; if (!c) { return (0); } r = c; do { *wp++ = *d++; } while (--c); if (wp == p->rb_end) { wp = p->rb_start; } p->rb_whold = wp; return (r); } /* Finally, deal with the case where data will wrap. Since the write pointer is never at the end of the buffer, there is always one element in the buffer. So, this copy doesn't require testing. */ r = c; n -= r; do { *wp++ = *d++; } while (--c); /* Next, copy data to the start of the buffer. This might not copy any data if rhold is at the start of the buffer. */ wp = p->rb_start; c = rh - wp; if (n < c) { c = n; } if (c) { r += c; do { *wp++ = *d++; } while (--c); } p->rb_whold = wp; return (r); } /* Returns the number of elements that may be put into the buffer. It is, in effect, a put routine, which means that you can't call it in a context where it might overlap with a put of the ring buffer. However, asynchronous gets may occur, which would increase the number of available elements to above what this routine returns. */ RB_QUAL size_t RB_NAME(pcount)(const RB_CONTROL *p) { RB_TYPE *rp = p->rb_rhold; RB_TYPE *wp = p->rb_whold; return (rp < wp ? p->rb_size - (wp - rp) : rp - wp); } /* This reads one datum from the ring buffer. The return value is the number of items returned, 0 or 1. */ RB_QUAL size_t RB_NAME(getc)(RB_CONTROL *p, RB_TYPE *d) { RB_TYPE *rp; rp = p->rb_rhold + 1; if (rp == p->rb_end) { rp = p->rb_start; } if (rp == p->rb_whold) { return (0); } *d = *rp; p->rb_rhold = rp; return (1); } /* This reads an arbitrary number of items from the ring buffer. The return value is the number of items returned. */ RB_QUAL size_t RB_NAME(gets)(RB_CONTROL *p, RB_TYPE *d, size_t n) { RB_TYPE *wh = p->rb_whold; RB_TYPE *rp; size_t c; size_t r; /* Handle the easy case, where the buffer is not wrapped. */ rp = p->rb_rhold + 1; if (rp == p->rb_end) { rp = p->rb_start; } if (rp <= wh) { c = wh - rp; if (n < c) { c = n; } if (!c) { return (0); } r = c; do { *d++ = *rp++; } while (--c); p->rb_rhold = rp - 1; return (r); } /* The buffer is wrapped, which means that the data to be returned might span the end of the buffer. This case applies when the data wanted will not span the end of the buffer. */ c = p->rb_end - rp; if (n <= c) { c = n; if (!c) { return (0); } r = c; do { *d++ = *rp++; } while (--c); p->rb_rhold = rp - 1; return (r); } /* The buffer is wrapped and so is the data that is to be returned. First, copy the data at the end of the buffer. */ r = c; n -= r; do { *d++ = *rp++; } while (--c); /* There might be nothing left to copy if whold is at the start of the buffer. */ rp = p->rb_start; c = wh - rp; if (n < c) { c = n; } if (c) { r += c; do { *d++ = *rp++; } while (--c); p->rb_rhold = rp - 1; } else { p->rb_rhold = p->rb_end - 1; } return (r); } /* This routine returns the number of data elements in the buffer. It is, in effect, a get routine, which means that you can't call it in a context where it might overlap with a get of the ring buffer. However, asynchronous puts may occur, which would increase the number of elements to above what this routine returns. */ RB_QUAL size_t RB_NAME(gcount)(const RB_CONTROL *p) { RB_TYPE *rp = p->rb_rhold + 1; RB_TYPE *wp = p->rb_whold; return (wp >= rp ? (wp - rp) : rp == p->rb_end ? wp - p->rb_start : p->rb_size - (rp - wp)); } /* This clears all data from a ring buffer. */ RB_QUAL void RB_NAME(gclear)(RB_CONTROL *p) { RB_TYPE *wp = p->rb_whold; p->rb_rhold = wp == p->rb_start ? p->rb_end - 1 : wp - 1; } #undef RB_PREFIX #undef RB_TYPE #undef RB_CONTROL #undef RB_QUAL #undef RB_GLUE1 #undef RB_GLUE #undef RB_NAME