;;; -*- Mode:LISP; Package:SERIAL-PROTO; Base:10; Readtable:ZL -*- #| Copyright GigaMos Systems, Inc. 1988 See filename "Copyright.Text" for licensing and release information. This is part of the implementation of the "Serial IP" interface for the new network system. This code implements the actual packet protocol and low-level serial I/O. EXPLANATION: A PACKET is expected to be an array of 8-byte words/characters whose fill-pointer indicates the number of characters already in the packet, and thus it is the index with which the next character should be read or written. The protocol is taken from a "Serial Line Interface" protocol by "rick@seismo.ARPA", which does not packetize I/O per se. Rather, a special character, SP-FRAME-END, indicates the end-of-packet. Another character, SP-FRAME-ESCAPE, is used to precede and "escape" special characters. However, the SP-FRAME-END and SP-FRAME-ESCAPE characters themselves are not send following an escape; translation characters, SP-FRAME-XEND and SP-FRAME-XESCAPE, are sent. Thus an SP-FRAME-ESCAPE or SP-FRAME-END received in the data stream always mean what they say. Any data loss or synchronization problem will not be detected by the protocol; rather, the user of these packets must figure out whether they got the right stuff. In the worst case, an escape or end character could be dropped; on the other hand, eventually an end character will be found, and the packets will be "in synch" again. This protocol has the advantage of low processing overhead. It is acceptable for serial Internet, I suppose, because the higher-level protocols check the packets and will retransmit as needed. |# (in-package "SERIAL-PROTO") (export '(*serial-proto-max-packet-size* make-serial-proto-stream new-serial-proto-packet serial-port-open-p serial-port-listen with-serial-port-allocated serial-proto-receive serial-proto-send)) ;;;Packet character constants: (defconstant *serial-proto-max-packet-size* #o1006) (defconstant *sp-frame-end* #o300) (defconstant *sp-frame-escape* #o333) (defconstant *sp-frame-xend* #o334) (defconstant *sp-frame-xescape* #o335) ;;;Utility functions ;;;Callers can use this to make a serial packet, or use some other array type (defun new-serial-proto-packet() (make-array (* 2 *serial-proto-max-packet-size*) :type 'art-string :initial-element 0. :fill-pointer 0)) ;;;These are definitely system-dependent; they don't belong here, ;;;but they don't belong anywhere else either. (defun serial-port-open-p(stream) (and stream #+LMI (and (funcall stream :input-unibus-channel) (funcall stream :output-unibus-channel)))) (defun serial-port-listen(stream) (and stream #+LMI (send stream :listen))) (defun serial-port-locate(&optional (name "sdu-serial-b")) (declare(values device)) (setq name (string-right-trim ":" name)) (find name si:all-shared-devices :key #'(lambda(dev)(send dev :name)) :test #'string-equal)) (defun serial-port-allocate(&optional (name "SDU-SERIAL-B")) (let((dev (serial-port-locate name))) (cond ((null dev) (ferror nil "No such device ~a" name)) ((not(typep dev 'si:sdu-serial-b-shared-device)) (ferror nil "Device ~s is not an SDU Serial device?")) ((null (progn (send dev :allocate) (send dev :allocate-if-easy))) nil) ((eq (car (send dev :lock)) si:current-process) ;;It's ours, or was dev) (t (or (null (car (send dev :lock))) (cerror "Free the device and hope the other user doesn't use it" "The serial device ~s~% is locked by ~s" dev (car(send dev :lock)))) (send dev :steal-lock) dev)))) (defun serial-port-deallocate(dev) (send dev :free-lock) (send dev :deallocate)) (defmacro with-serial-port-allocated(device-name &body body) `(let(dev) (unwind-protect (progn (setq dev (serial-port-allocate ,device-name)) ,@body) (and dev (serial-port-deallocate dev))))) #| Protocol I/O Stream This code uses DEFSELECT, which is similar to the Flavors system; it defines a function which dispatches on a message with, optionally, arguments. The stream created by MAKE-SERIAL-PROTO-STREAM is a function closure over special variables. A SERIAL-PROTO-OUTPUT-STREAM stream is never CLOSED by the protocol, but when its :CLOSE method is invoked, output is finished and null packets are sent. A SERIAL-PROTO-INPUT-STREAM is closed whenever a packet is sent; it must be "reOPENed" by sending it a :REOPEN message, and it should then be send a :PACKET message with 1 arg, the new packet array. |# ;;;SERIAL-PROTO--STREAM methods ;;Default handler (defun serial-proto-stream-pass(&rest args) (declare(special stream)) (apply stream args)) ;;Gettable/settable instance variables (defun serial-proto-stream-var(op &optional arg &rest ignore) (declare(special escaping closed status packet)) (ecase op (:escaping escaping) (:closed closed) (:status status) (:packet (when arg (setq packet arg)) packet))) ;;;Input methods (defun serial-proto-stream-full-packet(ignore) (declare(special packet closed status)) (when (>= (fill-pointer packet) (array-length packet)) (setq status (setq closed :full)))) (defun serial-proto-stream-tyi(&optional op &aux ch) (declare(special stream closed status escaping packet)) (serial-proto-stream-full-packet op) (when (not closed) (block nil (setq ch (funcall stream op)) (unless ch (return)) (setf (aref packet (fill-pointer packet)) (if (not escaping) (cond ((char= ch *sp-frame-end*) (setq closed(setq status :done)) (return)) ((char= ch *sp-frame-escape*) (print 'escaping) (setq escaping t) (return)) (t ch)) (cond ((char= ch *sp-frame-xend*) *sp-frame-end*) ((char= ch *sp-frame-xescape*) *sp-frame-escape*) (t ch)))) (setq escaping nil) (incf (fill-pointer packet)) ch))) ;;;Output methods (defun serial-proto-stream-tyo(ch) (declare(special stream closed)) (unless closed (cond ((null ch) nil) ((char= ch *sp-frame-escape*) (funcall stream :tyo *sp-frame-escape*) (funcall stream :tyo *sp-frame-xescape*)) ((char= ch *sp-frame-end*) (funcall stream :tyo *sp-frame-escape*) (funcall stream :tyo *sp-frame-xend*)) (t (funcall stream :tyo ch))))) (defun serial-proto-stream-finish-output(&optional ignore) (declare(special stream closed packet)) (unless closed (funcall stream :tyo *sp-frame-end*) ;;For the Lambda's sake we do :FORCE-OUTPUT to the serial stream when ;;we're done outputting a packet. This yields a minor improvement over ;;the default, where characters are forced out by :TYO and :STRING-OUT. (funcall stream :force-output)) (when (arrayp packet) (setf (fill-pointer packet) 0))) ;;;Close methods (defun serial-proto-stream-close(&optional ignore abort-p) (declare(special stream packet closed status)) (setq packet nil) (setq closed (setq status :closed)) (and (serial-port-open-p stream) (close stream abort-p))) (defun serial-proto-stream-close-output(&optional op abort-p) ;;This sends 3 null packets to tell the other side to shut down. (dotimes(p 3) (serial-proto-stream-finish-output)) ;;Now close as usual (serial-proto-stream-close op abort-p)) ;;;The stream select-methods (defselect (serial-proto-input-stream serial-proto-stream-pass) (:escaping . serial-proto-stream-var) (:closed . serial-proto-stream-var) (:status . serial-proto-stream-var) (:packet . serial-proto-stream-var) (:full-p . serial-proto-stream-full-packet) (:close . serial-proto-stream-close) (:finish ()) ;; ;;Input ;; (:tyi-no-hang . serial-proto-stream-tyi) (:tyi . serial-proto-stream-tyi) (:reopen () (declare(special closed status)) (setq closed nil) (setq status :open))) (defselect (serial-proto-output-stream serial-proto-stream-pass) (:escaping . serial-proto-stream-var) (:closed . serial-proto-stream-var) (:status . serial-proto-stream-var) (:packet . serial-proto-stream-var) (:full-p . serial-proto-stream-full-packet) (:close . serial-proto-stream-close-output) ;; ;;Output ;; (:tyo (ch) (serial-proto-stream-tyo ch)) (:finish . serial-proto-stream-finish-output) (:send-packet () (declare(special stream packet closed)) (unless closed (do*((indx 0 (1+ indx)) (ch (aref packet indx) (aref packet indx))) ((>= indx (fill-pointer packet)) (serial-proto-stream-finish-output) (serial-proto-stream-tyo ch)))))) (defun make-serial-proto-stream (stream mode &optional packet) " Depending on MODE, returns a SERIAL-PROTO--STREAM, which supports normal uni-directional stream operations, with SLIP protocol character handling. A PACKET is an array of 8-bit bytes. To initialize with a specified packet, you should set the packet's fill-pointer appropriately." (declare(special stream packet)) (declare(values serial-proto-io-stream)) (assert (typep mode '(member :input :output)) (mode) "Mode must be :INPUT or :OUTPUT") (setq packet (or packet (new-serial-proto-packet))) (let((escaping nil) (closed nil) (status :open) (direction mode) self) (declare(special escaping closed status direction self)) (setq self (closure '(stream packet escaping closed status direction self) (case mode (:input 'serial-proto-input-stream) (:output 'serial-proto-output-stream)))))) ;;;External interfaces ;;;Input: ;;; ;;;This routine packs characters into a PACKET if they are available. ;;;Initially the packet should have its fill-pointer set to 0. This ;;;probably must be called several times before the packet will be ;;;filled. The routine always returns whenever a :TYI-NO-HANG returns ;;;NIL, meaning there's no input. This behaviour means more passes ;;;through this routine, but nobody need ever hang waiting for input, ;;;and no time-out computation. We return non-NIL eventually when the ;;;packet is full or the end-of-packet character is received. After ;;;using the packet, the caller is responsible for sending a :REOPEN ;;;message to the stream before it can be used again. ;;; (defun serial-proto-receive(stream) (do((result (funcall stream :tyi-no-hang) (funcall stream :tyi-no-hang))) ((null result) (funcall stream :closed)))) ;;;Output: ;;; ;;;Send PACKET immediately on STREAM. ;;; (defun serial-proto-send(stream packet) (funcall stream :packet packet) (funcall stream :send-packet))