;;; -*- Mode:LISP; Package:SYSTEM-INTERNALS; Lowercase:T; Base:8; Readtable:ZL -*-


;;; >>>> NOT FOR ETHERNET

;;; user callable methods

;;;  :owner		NIL - free
;;;			:NOT-ON-BUS
;;;			0..31. slot number who owns the device
;;;
;;;  :quad-slot
;;;			for a NUBUS device, the quad slot the device is in, else NIL
;;;
;;;  :device-still-owned-by-me-p
;;;			T if I own the device
;;;			NIL if it's either :NOT-ON-BUS, free, or owned by someone else
;;;			This is supposed to be fast enough to be included in the main
;;;			loop that uses a device, e.g. every tape record.
;;;
;;;  :error-if-i-dont-own-device
;;;			If :device-still-owned-by-me-p would return NIL, this signals
;;;			the appropriate error.
;;;			This is also supposed to be fast.
;;;
;;;  :allocate-if-easy
;;;			Allocates the device and returns T if it is currently free.
;;;			Returns NIL if the device is not on the bus, or is owned by someone else.
;;;
;;;  :allocate
;;;			Allocates the device and returns T if it is currently free.
;;;			Signals the appropriate error if not.
;;;
;;;  :deallocate
;;;			If I own the device, free it, otherwise, just return

;;; to make a new shared device
;;;
;;; in a :BEFORE :INIT deamon, you have to set sys-conf-owner-index and sys-conf-slot-index
;;; if you want, you can add deamons, or replace the following methods
;;;
;;;  :after-allocation
;;;			gets called whenever this machine allocates the device, and
;;;			has reason to believe that it did not own it before.
;;;
;;;  :after-deallocation
;;;			gets called whenever this machine frees the device after having
;;;			owned it for a while
;;;
;;;  :warm-boot
;;;			gets sent to each instance of basic-shared-device
;;;			during the warm-initialization-list
;;;			you can add a deamon to this, but don't replace the primay method
;;;
;;;  :close
;;;			called when device is closed.  the default is to just send
;;;			:deallocate to self, but you can replace it
;;;
;;;  :close-for-process
;;;                     called with and argument PROCESS, this is like close
;;;                     except that it uses the specified process to to check against
;;;                     the value of the lock's process so that a process can free a lock
;;;                     not belonging to it. (i.e. the system menu process during a window's
;;;                     :BEFORE :KILL method).
;;;

(defflavor basic-shared-device
	   ((sys-conf-owner-index nil)
	    (owner-virtual-address nil)
	    (sys-conf-slot-index nil)
	    ;; i owned-it-last-time meanings (name of variable is poor choice).
	    ;;  NIL = cold booted state, device may be owned in SYS-CONF, maybe initialized itself
	    ;;        (maybe not, so you must assume that it is not), but certainly in-virtual-memory
	    ;;        structures have not been set up.
	    ;;  T   = device owned and initialized.
	    ;; :WARM = there has been a warm boot, which some initializations are undone by the SDU
	    ;;         when this happens, such as interrupt forwarding. Device should probably be
	    ;;         reinitialized.
	    (i-owned-it-last-time nil)
	    lock
	    (last-instance nil)
	    (default-flavor-and-init-options nil)
	    (name nil)
	    )
	   ()
  :gettable-instance-variables
  :settable-instance-variables
  (:method-combination (:case :set))
  )


(defvar all-shared-devices nil)

(defmethod (basic-shared-device :before :init) (ignore)
  (cond ((and (null sys-conf-owner-index)
	      (null owner-virtual-address))
	 (ferror nil "you must initialize sys-conf-owner-index when making the instance")))
  (push self all-shared-devices)
  (setq lock (%make-pointer dtp-locative (list nil)))
  )

;; Copied from LAD: RELEASE-3.SYS; SHARED-DEVICE.LISP#47 on 2-Oct-86 04:42:45
(defmethod (basic-shared-device :print-self) (stream print-depth slashify-p)
  print-depth
  (cond ((null slashify-p)
	 (send stream :string-out name))
	(t
	 (format stream "#~s ~s ~s"
		 (type-of self)
		 name
		 (if (sys-conf-structure-available-p)
		     (selectq (send self :owner)
		       (:not-on-bus "not on bus")
		       (nil "free")
		       (t (format nil "allocated by slot ~d." (send self :owner))))
		   "Not accessible")))))

(defmethod (basic-shared-device :read-instance) (ignore stream)
  (let ((nam (read stream)))
    (read stream)
    (fs:get-pathname-host nam)))

(defmethod (basic-shared-device :sys-conf-owner) (&aux lo hi)
  (cond ((null owner-virtual-address)
	 (setq lo (aref *sys-conf* (* 2 sys-conf-owner-index)))
	 (setq hi (aref *sys-conf* (1+ (* 2 sys-conf-owner-index)))))
	(t
	 (setq lo (%p-ldb (byte 16. 0) owner-virtual-address))
	 (setq hi (%p-ldb (byte 16. 16.) owner-virtual-address))))
  (cond ((and (= lo 177777) (= hi 177777))
	 -1)
	(t
	 (dpb hi 2020 lo))))


;remember to set the high bit if you are trying to say it is owned by a slot
(defmethod (basic-shared-device :set-sys-conf-owner) (new-owner)  
  (cond ((null owner-virtual-address)
	 (aset (ldb 0020 new-owner) *sys-conf* (* 2 sys-conf-owner-index))
	 (aset (ldb 2020 new-owner) *sys-conf* (1+ (* 2 sys-conf-owner-index))))
	(t
	 (%p-dpb (ldb 0020 new-owner) 0020 owner-virtual-address)
	 (%p-dpb (ldb 2020 new-owner) 2020 owner-virtual-address)))
  new-owner)

;setf seems to generate forms that can use this
(defmethod (basic-shared-device :case :set :sys-conf-owner) (new-owner)
  (send self :set-sys-conf-owner new-owner))

(defmethod (basic-shared-device :quad-slot) ()
  (if sys-conf-slot-index
      (get-quad-slot (aref *sys-conf* (* 2 sys-conf-slot-index)))
    nil))

(defmethod (basic-shared-device :after-allocation) ignore)

(defmethod (basic-shared-device :after-deallocation) ignore)

(defmethod (basic-shared-device :warm-boot) ()
  (setq i-owned-it-last-time :warm))

(defmethod (basic-shared-device :before-cold-boot) ()
  (setq i-owned-it-last-time nil))

(defun shared-device-warm-boot ()
  (dolist (x all-shared-devices)
    (send x :warm-boot)))

(defun shared-device-before-cold-boot ()
  (dolist (x all-shared-devices)
    (send x :before-cold-boot)))

(add-initialization "shared device warm boot" '(shared-device-warm-boot) '(warm))
(add-initialization "shared device before cold boot" '(shared-device-before-cold-boot) '(:before-cold))

(defun get-quad-slot (n)
  (cond ((zerop (logand #x80 n))
	 (ldb (byte 8 0) (logxor #xf0 n)))
	(t
	 (ldb (byte 8 0) n))))

(defun get-slot-index (n)
  (cond ((zerop (logand #x80 n))
	 (ldb (byte 8 0) n))
	(t
	 (ldb (byte 8 0) (logxor #xf0 n)))))

(defmethod (basic-shared-device :owner) ()
  "Returns either :NOT-ON-BUS, NIL if no one owns it, or a slot number (between 0..31.)."
  (if (sys-conf-structure-available-p)
      (let ((owner (send self :sys-conf-owner)))
	(cond ((eq owner -1)
	       :not-on-bus)
	      ((ldb-test (byte 1 31.) owner)
	       (get-slot-index owner))
	      (t nil)))
    :not-on-bus))

(defmethod (basic-shared-device :device-still-owned-by-me-p) ()
  (let ((owner (send self :owner)))
    (and (numberp owner)
	 (= owner (get-slot-index rg-quad-slot)))))

(defmethod (basic-shared-device :error-if-i-dont-own-device) ()
  (cond ((null (send self :device-still-owned-by-me-p))
	 (cond ((eq (send self :owner) :not-on-bus)
		(signal-proceed-case
		  (() 'device-not-on-bus
		      "The device ~a disappeared from the bus!"
		      self)
		  (:reallocate
		   (send self :allocate-if-easy t))))
	       (t
		(signal-proceed-case
		  (() 'device-already-allocated
		      "The ~a device was stolen by slot ~d."
		      self
		      (send self :owner))
		  (:reallocate
		   (send self :allocate-if-easy t))))))))


(defmethod (basic-shared-device :can-allocate-p) ()
  (let ((owner (send self :owner)))
    (or (null owner)
	(eq owner (get-slot-index rg-quad-slot)))))

(defmethod (basic-shared-device :allocate-if-easy) (&optional steal-it)
  (let ((slot (send self :owner)))
    (cond ((null slot)
	   (send self :set-sys-conf-owner
		 (dpb 1 (byte 1 31.) (get-slot-index rg-quad-slot)))
	   (send self :after-allocation)
	   (setq i-owned-it-last-time t))
	  ((eq slot (get-slot-index rg-quad-slot))
	   (if (memq i-owned-it-last-time '(nil :warm))
	       (send self :after-allocation))
	   (setq i-owned-it-last-time t))
	  (steal-it
	   (send self :set-sys-conf-owner
		 (dpb 1 (byte 1 31.) (get-slot-index rg-quad-slot)))
	   (send self :after-allocation)
	   (setq i-owned-it-last-time t)))))

(defmethod (basic-shared-device :allocate) (&optional steal-it)
  (prog ()
     again
	(cond ((null (send self :allocate-if-easy steal-it))
	       (cond ((eq (send self :owner) :not-on-bus)
		      (signal-proceed-case
			(() 'device-not-on-bus
			    "Device ~a is not on the bus"
			    self)
			(:allocate-anyway
			 (send self :allocate-if-easy t))))
		     (t
		      (signal-proceed-case
			(() 'device-already-allocated
			    "Device ~a is already allocated by the processor in slot ~d."
			    self
			    (send self :owner))
			(:steal-device
			 (send self :allocate-if-easy t))
			(:try-allocating-again
			 (go again)))))))))

(defmethod (basic-shared-device :deallocate) ()
  (setq i-owned-it-last-time nil)
  (cond ((send self :device-still-owned-by-me-p)
	 (send self :after-deallocation)
	 (send self :set-sys-conf-owner 0))))

(defmethod (basic-shared-device :close-for-process) (abortp for-process)
  abortp
  (send self :deallocate)
  (send self :free-lock for-process))

(defmethod (basic-shared-device :close) (&optional abortp)
  abortp
  (send self :deallocate)
  (send self :free-lock))

(defmethod (basic-shared-device :get-lock) (&optional (wait-if-necessary t))
  (cond ((eq (car lock) current-process)
	 t)
	((%store-conditional lock nil current-process)
	 t)
	(wait-if-necessary
	 (process-lock lock nil (format nil "~a locked" self))
	 (rplaca lock current-process)		;in case someone returns from process-lock
	 t)
	(t
	 nil)))

(defmethod (basic-shared-device :steal-lock) ()
  (rplaca lock current-process))

(defmethod (basic-shared-device :free-lock) (&optional (for-process current-process))
  (cond ((null (car lock)))
	((null (%store-conditional lock for-process nil))
	 (ferror nil "free-lock called while I don't own the lock"))))


;----

(defflavor shared-device
	   (pathname-flavor
	    (system-type 'shared-device))
	   (basic-shared-device si:basic-host si:property-list-mixin)
;required init option :pathname-flavor
  (:gettable-instance-variables pathname-flavor system-type)
  (:initable-instance-variables pathname-flavor system-type)
  )

(defmethod (shared-device :pathname-host-namep) (test-name)
  (string-equal name test-name))

(defmethod (shared-device :open) (flavor-and-init-options shared-device-pathname)
  (cond ((null flavor-and-init-options)
	 (setq flavor-and-init-options default-flavor-and-init-options)))
  (let ((flavor (car flavor-and-init-options))
	(init-options (cdr flavor-and-init-options)))
    (setf (getf init-options :shared-device) shared-device-pathname)
    (send self :allocate)
    (send self :get-lock)
    (cond ((null flavor-and-init-options)
	   shared-device-pathname)
	  ((eq (car flavor-and-init-options) (type-of last-instance))
	   last-instance)
	  (t
	   (setq last-instance (apply 'make-instance flavor init-options))))))

(defflavor shared-device-pathname
	   ()
	   (fs:pathname)
  )

(defmethod (shared-device-pathname :string-for-printing) ()
  (string-append (send (send self :host) :name) ":")
  )

(defmethod (shared-device-pathname :parse-namestring) (ignore namestring &optional (start 0) end)
  (declare (values (device directory name type version parse-end)))
  (values :unspecific
	  :unspecific
	  (string-upcase (substring namestring start end))
	  :unspecific
	  :unspecific)
  )

(defmethod (shared-device-pathname :homedir) (&optional ignore)
  self)

(defmethod (shared-device-pathname :open) (filename &key &optional
					   flavor-and-init-options
					   &allow-other-keys)
  (send (send filename :host) :open flavor-and-init-options self))

(defmethod (shared-device-pathname :close) (&optional abortp)
  (send (send self :host) :close abortp)
  )

(compile-flavor-methods shared-device-pathname)

(defun add-shared-device (&key &optional
			  (shared-device-flavor 'shared-device)
			  name
			  sys-conf-owner-index
			  owner-virtual-address
			  sys-conf-slot-index
			  property-list
			  (pathname-flavor 'shared-device-pathname)
			  default-flavor-and-init-options)
  (setq fs:*pathname-host-list*
	(del #'(lambda (x y) (string-equal x (send y :name))) name fs:*pathname-host-list*))
  (push (make-instance shared-device-flavor
		       :name name
		       :sys-conf-owner-index sys-conf-owner-index
		       :owner-virtual-address owner-virtual-address
		       :sys-conf-slot-index sys-conf-slot-index
		       :pathname-flavor pathname-flavor
		       :default-flavor-and-init-options default-flavor-and-init-options
		       :property-list property-list
		       )
	fs:*pathname-host-list*))

;-----

(defflavor sdu-serial-b-shared-device
	   ()
	   (shared-device)
  )

(defvar *sdu-serial-stream-interrupt-alist*
	'(("SDU-SERIAL-B" %SDU-PORT-B-RCV %SDU-PORT-B-XMIT)
	  ("SDU-SERIAL-A" %SDU-PORT-A-RCV %SDU-PORT-A-XMIT)))

(defmethod (sdu-serial-b-shared-device :after-allocation) ()
  (DOLIST (I (CDR (OR (ASS #'STRING-EQUAL (SEND SELF :NAME) *sdu-serial-stream-interrupt-alist*)
		      (FERROR NIL "internal error, no interrupts known for this device"))))
    (forward-sdu-interrupt-to-lambda (EVAL I))))

(defmethod (sdu-serial-b-shared-device :before :open) (flavor-and-init-options shared-device-pathname)
  (let ((flavor (car flavor-and-init-options))
	(init-options (cdr flavor-and-init-options)))
    (when init-options
      (setf (getf init-options :shared-device) shared-device-pathname)
      (setq last-instance (apply 'make-instance flavor init-options)))))

(defmethod (sdu-serial-b-shared-device :after :open) (&rest ignore)
  (send last-instance :reset))

(defmethod (sdu-serial-b-shared-device :close) (&optional ignore)
  (send self :free-lock)
  (send self :deallocate)
  )

(defmethod (sdu-serial-b-shared-device :close-for-process) (ignore for-process)
  (send self :free-lock for-process)
  (send self :deallocate)
  )

(compile-flavor-methods sdu-serial-b-shared-device)

(add-shared-device :name "SDU-SERIAL-B"
		   :shared-device-flavor 'sdu-serial-b-shared-device
		   :sys-conf-owner-index %system-configuration-sdu-serial-b-owner
		   :default-flavor-and-init-options '(sdu-serial-stream))


(add-shared-device :name "SDU-SERIAL-A"
		   :shared-device-flavor 'sdu-serial-b-shared-device
		   :sys-conf-owner-index %system-configuration-sdu-serial-a-owner
		   :default-flavor-and-init-options '(sdu-serial-stream))

;-----

(defflavor half-inch-tape-shared-device
	   ()
	   (shared-device)
  )

(defmethod (half-inch-tape-shared-device :after-allocation) ()
  ;(fs:tm-init-internal)
  )

(compile-flavor-methods half-inch-tape-shared-device)

(add-shared-device :name "HALF-INCH-TAPE"
		   :shared-device-flavor 'half-inch-tape-shared-device
		   :sys-conf-owner-index %system-configuration-tapemaster-owner
		   )

;-----

(defflavor medium-resolution-color-shared-device
	   ()
	   (shared-device))

(defmethod (medium-resolution-color-shared-device :after-allocation) ()
  (set (intern "GREY-PROM-PLIST" "GREY") nil)
  (funcall (intern "DOWNLOAD-GREY-BOARD" "GREY")))

#||

(defmethod (medium-resolution-color-shared-device :warm-boot) ()
  ())

(undefmethod (medium-resolution-color-shared-device :warm-boot))

||#

(compile-flavor-methods medium-resolution-color-shared-device)

(add-shared-device :name "MEDIUM-RESOLUTION-COLOR"
		   :shared-device-flavor 'medium-resolution-color-shared-device
		   :sys-conf-owner-index %system-configuration-grey-owner
		   :sys-conf-slot-index %system-configuration-grey-slot
		   )

;-----

(defflavor excelan-network-interface-shared-device
	   ()
	   (shared-device))

(compile-flavor-methods excelan-network-interface-shared-device)

(add-shared-device :name "EXCELAN-NETWORK-INTERFACE"
		   :shared-device-flavor 'excelan-network-interface-shared-device
		   :sys-conf-owner-index %system-configuration-excelan-owner)

