;;; -*- Mode:LISP; Package:USER; Base:10; Fonts:(CPTFONTB) -*-

#|| 
   this isnt a fatal bug, but it shows an important
   performance limitation. 8-Feb-86 11:27:39 -gjc

A standard way to do a multi-threaded device (MTD) or network driver is as follows:

(defun User-Read/Write-function (data)
  (using-resource (s device-structure)
    (with-lock (device-lock)
      (device-transmit s data))
    (device-reply-wait s)))

The device-transmit function may actually write the device, allowed because of the 
1device-lock*, however a background process must wait on device replies so
as to demultiplex different responses and update the cooresponding device-structure.
Another process could also take responsibility for retransmission,
although the device-reply-wait could check for that too. 
The overhead involved is in the code for allocating the resource, getting
the lock, and the process switches involved.

 [USER] ==> [SCHEDULER] ==> [BACKGROUND] ==> [SCHEDULER] ==> [USER]

4 Stack-Group switches and two runs of the scheduler.

A single threaded (STD) implementation would require:

 [USER] ==> [SCHEDULER] ==> [USER]

Although if the device responds quickly enough the process-wait it uses
may return the first time before the 2 stack-group switches are needed.

The lisp-level 3COM ethernet driver falls into the MTD class.
The addition of microcode support put the threading and demultiplexing
responsibility on the microcode.

Another way to optimize the MTD case is to do a quick-check,

 [USER] ==> [SCHEDULER] ==> [BACKGROUND] ==> [SCHEDULER] ==> [USER]
   $                                                            ^
   $                                                            ^
   [quick-check, device ready and for me?]----------------------^

The trick to pulling this off is organizing the device driver so
that the cases in the background are available to the forground.
e.g.

(defun background-process ()
  (do-forever
    (process-wait "device" #'device-wants-to-say-something)
    (handle-message (get-device-message))))

(defun handle-message (message)
  (dolist (request outstanding-requests)
    (if (message-for-request message request)
        (enact-request message request))))

(defun device-wants-to-say-something ()
  (or (actual-device-wants-to-say-something)
      *leftover-message*))

(defun get-device-message ()
  (if *leftover-message*
      (prog1 *leftover-message* (setq *leftover-message* nil))
    (get-actual-device-message)))


The quick-check could then be:

(defun quick-check (request)
  (actual-device-wants-to-say-something)
  (let ((message (get-actual-device-message)))
    (cond ((message-for-request message request)
           (enact-request message request))
	  ('else
	   (setq *leftover-message* message)))))


The purpose of the MTD-TEST is to get a base-line figure for the
overhead of the processe and stack group switches, so you can determine
if your application might be performance limited by that. If so
you might rewrite it only the lines suggested above.

||#


(defvar *mtd-lock* nil)
(defvar *mtd-outstanding* nil)

(defstruct (mtd-structure conc-name named)
  message
  reply)

(defresource mtd-structure ()
  :constructor (make-mtd-structure)
  :deinitializer (progn (setf (mtd-structure-message object) nil)
			(setf (mtd-structure-reply object) nil)))

(defun mtd-test (&optional (loops 100))
  (enable-mtd-background)
  (time-loop-report "MTD" loops
		    #'(lambda ()
			(using-resource (x mtd-structure)
			  (with-lock (*mtd-lock*)
			    (mtd-transmit x))
			  (mtd-wait x)))))


(defun mtd-test-control (&optional (loops 1000))
  (time-loop-report "MTD control" loops
		    #'(lambda ()
			(using-resource (x mtd-structure)
			  (with-lock (*mtd-lock*)
			    (without-interrupts
			      (mtd-transmit x)
			      (mtd-receive)))))))

(defvar *buff1* (make-array 2048 :type 'art-8b))
(defvar *buff2* (make-array 2048 :type 'art-8b))

(defun mtd-test-buff (&optional (loops 100))
  "Test of transmit with 2048 byte buffer"
  (enable-mtd-background)
  (time-loop-report "MTD" loops
		    #'(lambda ()
			(using-resource (x mtd-structure)
			  (with-lock (*mtd-lock*)
			    (copy-array-portion *buff1*
						0
						(length *buff1*)
						*buff2*
						0
						(length *buff2*))
			    (mtd-transmit x))
			  (mtd-wait x)))))

(defun mtd-transmit (x)
  (without-interrupts
    (push x *mtd-outstanding*)))

(defun mtd-wait (x)
  (process-wait "mtd wait"
		#'(lambda (y)
		    (mtd-structure-reply y))
		x))

     
(defvar *mtd-background* nil)

(defun enable-mtd-background ()
  (or *mtd-background*
      (setq *mtd-background* (make-process "mtd background")))
  (send *mtd-background* :preset 'mtd-background)
  (process-reset-and-enable *mtd-background*))

(defun mtd-background ()
  (do-forever
    (mtd-device-wait)
    (mtd-receive)))

(defun mtd-receive ()
  (dolist (r *mtd-outstanding*)
    (when (mtd-request-p r)
      (without-interrupts
	(setq *mtd-outstanding* (delq r *mtd-outstanding*)))
      (return (mtd-enact r)))))
  

(defun mtd-request-p (r)
  r
  t)

(defun mtd-device-wait ()
  ;; this being fake, we just want for an outstanding request.
  (process-wait "mtd interrupt" #'(lambda ()
				    *mtd-outstanding*)))

(defun mtd-enact (r)
  (setf (mtd-structure-reply r) t))


(defun time-loop-report (name loops function)
  (let ((time (time)))
    (dotimes (j loops)
      (funcall function))
    (setq time (quotient (time-difference (time) time) 60.0))
    (format t "~&~A of ~D loops took ~$ seconds, ~$ loops per second~%"
	    name
	    loops time
	    (quotient loops time))))

  
