;;; -*- Mode:Lisp; Readtable:CL; Package:USER; Base:10; Patch-File:T -*-
;;; Patch file for CDI version 1.12
;;; Reason:
;;;  NuPI tape support.
;;; Written 7-Jul-86 10:20:06 by Gibson at site CDI Dallas
;;; while running on EXPLORER-1 from band 1
;;; with System 110.232, Lambda-Diag 7.17, Experimental Local-File 68.7, FILE-Server 18.4, Unix-Interface 9.1, ZMail 65.14, Object Lisp 3.4, Tape 6.39, Site Data Editor 3.3, Tiger 24.0, KERMIT 31.3, Window-Maker 1.1, Gateway 4.8, TCP-Kernel 39.7, TCP-User 62.7, TCP-Server 45.5, MEDIUM-RESOLUTION-COLOR 3.4, MICRO-COMPILATION-TOOLS 3.2, System Revision Level 3.26, Experimental CDI 1.11, microcode 1525, CDI LambdaE base.



; From file S2: >Lambda-3>TAPE>NUPI-SUPPORT.LISP.13 at 7-Jul-86 10:20:07
#10R SYSTEM-INTERNALS#: 
(COMPILER-LET ((*PACKAGE* (PKG-FIND-PACKAGE "SYSTEM-INTERNALS")))
  (COMPILER::PATCH-SOURCE-FILE "SYS: tape; nupi-support lisp "

(defmacro dma-buffer-aref-32 (array index)
  (Once-Only (index)
    `(dpb (aref (dma-buffer-16b ,array) (1+ (* 2 ,index)))
	  (byte 16 16)
	  (aref (dma-buffer-16b ,array) (* 2 ,index))))
  )

))

; From file S2: >Lambda-3>TAPE>NUPI-SUPPORT.LISP.13 at 7-Jul-86 10:20:33
#10R SYSTEM-INTERNALS#: 
(COMPILER-LET ((*PACKAGE* (PKG-FIND-PACKAGE "SYSTEM-INTERNALS")))
  (COMPILER::PATCH-SOURCE-FILE "SYS: tape; nupi-support lisp "

(defmacro dma-buffer-set-aref-32 (array index value)
  (Once-Only (index)
    `(progn
       (aset (ldb (byte 16 0) ,value) (dma-buffer-16b ,array) (* 2 ,index))
       (aset (ldb (byte 16 16) ,value) (dma-buffer-16b ,array) (1+ (* 2 ,index)))
       ,value))
  )

(defsetf dma-buffer-aref-32 dma-buffer-set-aref-32)

;;; Is this good enough?
(Defun DMA-Buffer-P (x)
  (arrayp x))

(Defun %Wired-Status? (page-status)
  (eq (ldb %%pht1-swap-status-code page-status) %pht-swap-status-wired))

(Defun %Page-Wired? (pointer)
  (%Wired-Status? (%page-status pointer)))

;;;
;;; nupi command blocks
;;;

(defun get-nupi-command-block (ignore)
  (let ((command-block (get-dma-buffer 1)))
    (setf (dma-buffer-named-structure-symbol command-block) 'nupi-command-block)
    command-block))

(defun free-nupi-command-block (command-block)
  (free-dma-buffer command-block))

(defconst %nupi-command-word 0)
(defconst %nupi-status-word 1)
(defconst %nupi-scatter-list 2)
(defconst %nupi-transfer-count 3)
(defconst %nupi-logical-block 4)
(defconst %nupi-interrupt-address 5)
(defconst %nupi-reserved-a 6)
(defconst %nupi-reserved-b 7)

(defmacro nupi-command-word (command-block)
  `(dma-buffer-aref-32 ,command-block %nupi-command-word))

(defmacro nupi-status-word (command-block)
  `(dma-buffer-aref-32 ,command-block %nupi-status-word))

(defmacro nupi-scatter-list (command-block)
  `(dma-buffer-aref-32 ,command-block %nupi-scatter-list))

(defmacro nupi-transfer-count (command-block)
  `(dma-buffer-aref-32 ,command-block %nupi-transfer-count))

(defmacro nupi-logical-block (command-block)
  `(dma-buffer-aref-32 ,command-block %nupi-logical-block))

(defmacro nupi-interrupt-address (command-block)
  `(dma-buffer-aref-32 ,command-block %nupi-interrupt-address))

(defmacro nupi-reserved-a (command-block)
  `(dma-buffer-aref-32 ,command-block %nupi-reserved-a))

(defmacro nupi-reserved-b (command-block)
  `(dma-buffer-aref-32 ,command-block %nupi-reserved-b))

(defselect ((nupi-command-block named-structure-invoke))
  (:print-self (array stream ignore ignore)
    (printing-random-object (array stream :typep)
      (When (%page-wired? array)
	(format stream "Wired #x~x; "
		(vadr-to-nubus-phys (dma-buffer-data-vadr array))))
      (format stream "Command: ~a" (or (cadr (assq (ldb (byte 8 24) (nupi-command-word array))
						   nupi-commands))
				       "Unknown"))
      (format stream "; Unit #x~x" (ldb (byte 8 0) (nupi-command-word array)))
      (Let ((status (nupi-status-word array))
	    list)
	(When (ldb-test (byte 1 31) status)
	  (push "Busy" list))
	(When (ldb-test (byte 1 30) status)
	  (push "Complete" list))
	(When (ldb-test (byte 1 29) status)
	  (push "Error" list))
	(when list
	  (format stream "; Status: ")
	  (format:print-list stream "~a" list)))
      
      (Format Stream "; Count ~d." (nupi-transfer-count array))
      (format stream "; Block ~d." (nupi-logical-block array))))
  (:describe (array)
    (format t "~&~S:" array)
    (let ((command-word (nupi-command-word array))
	  (status (nupi-status-word array))
	  (scatter-list (nupi-scatter-list array))
	  (transfer-count (nupi-transfer-count array))
	  (block (nupi-logical-block array))
	  (interrupt-address (nupi-interrupt-address array))
	  (reserved-a (nupi-reserved-a array))
	  (reserved-b (nupi-reserved-b array)))

      (format t "~&Unit #x~x" (ldb (byte 8 0) command-word))
      (format t "~&Spare ~s" (ldb (byte 8 8) command-word))
      (format t "~&Options ~s" (ldb (byte 8 16) command-word))
      (if (ldb-test (byte 1 20) command-word) (format t " Swap-partial-completion-interrupt"))
      (if (ldb-test (byte 1 21) command-word) (format t " device-address-is-physical"))
      (if (ldb-test (byte 1 22) command-word) (format t " SCATTER"))
      (if (ldb-test (byte 1 23) command-word) (format t " Interrupt-enable"))
      (format t "~&Command #x~16r ~a" (ldb (byte 8 24) command-word)
	      (cadr (assq (ldb (byte 8 24) command-word) nupi-commands)))

      (format t "~&Status ~s" status)
      (format t "~&  Busy ~s" (ldb (byte 1 31) status))
      (format t "~&  Complete ~s" (ldb (byte 1 30) status))
      (format t "~&  Error ~s" (ldb (byte 1 29) status))
      (format t "~&  Retries required ~s" (ldb (byte 1 28) status))
      (format t "~&  Aux status available ~s" (ldb (byte 1 27) status))
      (format t "~&  Paging partial completion ~s" (ldb (byte 1 26) status))
      (format t "~&  spare ~s" (ldb (byte 2 24) status))
      (let ((error (ldb (byte 8 16) status)))
	(format t "~&  controller error ~s" error)
	(when (not (zerop error))
	  (format t "  Class: \"~a\"" (nth (ldb (byte 3 21) status) nupi-error-classes))
	  (format t "  ~a" (cadr (assq error nupi-controller-errors)))
	  ))
      
      (let ((error (ldb (byte 8 8) status)))
	(format t "~&  device error ~s" error)
	(when (not (zerop error))
	  (format t "  Class \"~a\"" (nth (ldb (byte 3 13) status) nupi-error-classes))
	  (format t "  ~a " (cadr (assq error nupi-device-errors)))
	  ))
      (format t "~&  spare ~s" (ldb (byte 3 5) status))
      (format t "~&  ECC applied ~s" (ldb (byte 1 4) status))
      (format t "~&  n-retries ~s" (ldb (byte 3 0) status))

      (format t "~&scatter-list #x~16r" scatter-list)

      (format t "~&Transfer count ~d." transfer-count)
      (format t "~&Device block address ~s" block)
      (format t "~&Interrupt address #x~16r" interrupt-address)
      (format t "~&Reserved ~s ~s" reserved-a reserved-b)

      (when (ldb-test (byte 1 22) command-word)	;scatter bit
	(format t "~&Scatter list: ")
	(if (not (= (vadr-to-nubus-phys (%pointer-plus
					  (dma-buffer-data-vadr array)
					  8))
		    scatter-list))
	    (format t "~&   *** warning, scatter list doesn't really point here ***"))
	(do ((scatter-index 8 (+ scatter-index 2))
	     (pages-to-go (floor transfer-count 1024) (1- pages-to-go)))
	    ((zerop pages-to-go))
	  (format t "~&#x~8x ~d."
		  (dma-buffer-aref-32 array scatter-index)
		  (dma-buffer-aref-32 array (1+ scatter-index)))))
      ))
    
  (:which-operations (ignore)
    '(:print-self :which-operations :describe))
  )


(defun fill-in-nupi-command (command-block phys-unit command byte-count disk-address
			     dma-buffer dma-buffer-offset-in-pages
			     &aux n-pages)

  (When (or (not (%page-wired? command-block))
	    (and (dma-buffer-p dma-buffer)
		 (not (%page-wired? dma-buffer))))
    (ferror nil "COMMAND-BLOCK and DMA-BUFFER must be wired."))
  
  (Unless (zerop (ldb (byte 10 0) byte-count))
    (ferror nil "byte-count must be an even number of pages"))

  (setq n-pages (floor byte-count 1024))

  (When (and (dma-buffer-p dma-buffer)
	     (or (> (+ dma-buffer-offset-in-pages n-pages)
		    (dma-buffer-size-in-pages dma-buffer))
		 (> n-pages (floor (- page-size 8) 2))))	;number of scatter entries available
    (ferror nil "transfer request too big"))

  ;; really just need to clear first 8 words
  ;;  can't use array-initialize on 32b array, since it stores DTP-FIX tags
  (array-initialize (dma-buffer-16b command-block) 0)

  (setf (nupi-command-word command-block)
	(+ phys-unit
	   (dpb command (byte 8 24) (if (dma-buffer-p dma-buffer) #x400000 0))))	;scatter flag

  (setf (nupi-scatter-list command-block)
	(If (dma-buffer-p dma-buffer)
	    (vadr-to-nubus-phys (%pointer-plus
				  (dma-buffer-data-vadr command-block)
				  8))
	  (or dma-buffer 0)))

  (setf (nupi-transfer-count command-block) byte-count)
  (setf (nupi-logical-block command-block) disk-address)

  (when (dma-buffer-p dma-buffer)
    (do ((vadr (%pointer-plus (dma-buffer-data-vadr dma-buffer)
			      (* dma-buffer-offset-in-pages page-size))
	       (%pointer-plus vadr page-size))
	 (scatter-entry 8 (+ scatter-entry 2))
	 (pages-to-go n-pages (1- pages-to-go)))
	((zerop pages-to-go))
      (let ((padr (vadr-to-nubus-phys vadr)))
	(setf (dma-buffer-aref-32 command-block scatter-entry) padr)
	(setf (dma-buffer-aref-32 command-block (1+ scatter-entry)) 1024)))))

(defun fill-in-nupi-simple-command (command-block phys-unit command byte-count disk-address
			     dma-buffer dma-buffer-offset-in-pages
			     &aux n-pages)

  (When (or (not (%page-wired? command-block))
	    (and (dma-buffer-p dma-buffer)
		 (not (%page-wired? dma-buffer))))
    (ferror nil "COMMAND-BLOCK and DMA-BUFFER must be wired."))
  
;;;  (Unless (zerop (ldb (byte 10 0) byte-count))
;;;    (ferror nil "byte-count must be an even number of pages"))

  (setq n-pages (floor byte-count 1024))

  (When (and (dma-buffer-p dma-buffer)
	     (or (> (+ dma-buffer-offset-in-pages n-pages)
		    (dma-buffer-size-in-pages dma-buffer))
		 (> n-pages (floor (- page-size 8) 2))))	;number of scatter entries available
    (ferror nil "transfer request too big"))

  ;; really just need to clear first 8 words
  ;;  can't use array-initialize on 32b array, since it stores DTP-FIX tags
  (array-initialize (dma-buffer-16b command-block) 0)

  (setf (nupi-command-word command-block)
	(+ phys-unit
	   (dpb command (byte 8 24) 0)))

  (setf (nupi-scatter-list command-block)
	(Cond ((dma-buffer-p dma-buffer)
	       (vadr-to-nubus-phys (%pointer-plus
				     (dma-buffer-data-vadr command-block)
				     8)))
	      ((Null dma-buffer) 0)
	      (t
	       (si:dma-buffer-set-aref-32 command-block (1+ si:%nupi-reserved-b) dma-buffer)
	       (vadr-to-nubus-phys (%pointer-plus
				     (dma-buffer-data-vadr command-block)
				     8)))))

  (setf (nupi-transfer-count command-block) byte-count)
  (setf (nupi-logical-block command-block) disk-address)
  )

(defun start-nupi-command (command-block phys-unit command byte-count disk-address
			   dma-buffer dma-buffer-offset-in-pages
			   set-modified)
  (wire-wireable-array command-block 0 nil nil nil)
  ;;could arrange to do DONT-BOTHER-PAGING-IN on all pages but first
  (when (dma-buffer-p dma-buffer)
    (wire-wireable-array dma-buffer 0 nil set-modified nil))
  (fill-in-nupi-command
    command-block phys-unit command byte-count disk-address dma-buffer dma-buffer-offset-in-pages)
  (NuPI-Command-Initiate command-block)
  )

(defun start-nupi-simple-command (command-block phys-unit command byte-count disk-address
			   dma-buffer dma-buffer-offset-in-pages
			   set-modified)
  (wire-wireable-array command-block 0 nil nil nil)
  ;;could arrange to do DONT-BOTHER-PAGING-IN on all pages but first
  (when (dma-buffer-p dma-buffer)
    (wire-wireable-array dma-buffer 0 nil set-modified nil))
  (fill-in-nupi-simple-command
    command-block phys-unit command byte-count disk-address dma-buffer dma-buffer-offset-in-pages)
  (NuPI-Command-Initiate command-block)
  )

;;; This should be fixed up for the tape unit to use io-proceed.
(Defun NuPI-Command-Initiate (command-block)
  (Without-Interrupts
    (aref command-block 0)
    (%nubus-write #xF2 #xE00004
      (vadr-to-nubus-phys
	(%pointer-plus command-block (array-data-offset command-block)))))
  )


(Defun NuPI-Command-Complete-P (command-block)
  (ldb-test (byte 1 30.) (nupi-status-word command-block)))

(Defun NuPI-Command-Error-P (command-block)
  (ldb-test (byte 1 29.) (nupi-status-word command-block)))

(Defun NuPI-Check-Status (command-block)
  (Let ((device-status (ldb #o1010 (si:nupi-status-word command-block)))
	(unit (ldb (byte 8 0) (si:nupi-command-word command-block)))
	(data-transferred (si:nupi-transfer-count command-block)))
    (Selectq device-status
      ((0 #x48 #xC8)   ; no error, SCSI sense available, correctable data error: ignore.
       (When (NuPI-Command-Error-P command-block)
	 (ferror nil "nupi error"))
       nil)
      ((#x4C #x4D #x4E #x4F)
       (signal 'tape:filemark-encountered
	       :device-type 'nupi-device
	       :unit unit
	       :data-transferred data-transferred))
      ((#x4A #x4B) ; end of tape, end of recorded media
       (signal 'tape:physical-end-of-tape
	       :device-type 'nupi-device
	       :unit unit
	       :data-transferred data-transferred))))
  )


(Defun wait-for-nupi-command (command-block &optional (command "NuPI Wait"))
  (process-wait command #'NuPI-Command-Complete-P command-block)
  (NuPI-Check-Status command-block))

(Defun finish-nupi-command (command-block dma-buffer)
  (unwire-wireable-array command-block 0 nil)
  (When (dma-buffer-p dma-buffer)
    (unwire-wireable-array dma-buffer 0 nil)))

(defun nupi-logical-to-physical-unit (logical-unit)
  (dpb (ldb (byte 3 1) logical-unit)
       (byte 3 3)
       (ldb (byte 1 0) logical-unit)))

(defun simple-nupi-command (command-block command logical-unit disk-address byte-count
			    dma-buffer dma-buffer-offset-in-pages
			    set-modified &optional (command-name "NuPI Wait"))
  (start-nupi-simple-command command-block
		      (nupi-logical-to-physical-unit logical-unit)
		      command
		      byte-count
		      disk-address
		      dma-buffer
		      dma-buffer-offset-in-pages
		      set-modified)
  (wait-for-nupi-command command-block command-name)
  (finish-nupi-command command-block dma-buffer))

(defun nupi-read-from-disk (command-block logical-unit disk-address byte-count dma-buffer dma-buffer-offset-in-pages)
  (start-nupi-command command-block
		      (nupi-logical-to-physical-unit logical-unit)
		      #x12
		      byte-count
		      disk-address
		      dma-buffer
		      dma-buffer-offset-in-pages
		      t)
  (wait-for-nupi-command command-block "Disk Read")
  (finish-nupi-command command-block dma-buffer)
  )

(defun nupi-write-to-disk (command-block logical-unit disk-address byte-count dma-buffer dma-buffer-offset-in-pages)
  (start-nupi-command command-block
		      (nupi-logical-to-physical-unit logical-unit)
		      #x13
		      byte-count
		      disk-address
		      dma-buffer
		      dma-buffer-offset-in-pages
		      t)
  (wait-for-nupi-command command-block "Disk Write")
  (finish-nupi-command command-block dma-buffer)
  )

(defun nupi-read-from-tape (command-block logical-unit disk-address byte-count dma-buffer dma-buffer-offset-in-pages)
  (start-nupi-command command-block
		      (nupi-logical-to-physical-unit logical-unit)
		      #x12
		      byte-count
		      disk-address
		      dma-buffer
		      dma-buffer-offset-in-pages
		      t)
  (wait-for-nupi-command command-block "Tape Read")
  (finish-nupi-command command-block dma-buffer)
  )
  
(defun nupi-write-to-tape (command-block logical-unit disk-address byte-count dma-buffer dma-buffer-offset-in-pages)
  (start-nupi-command command-block
		      (nupi-logical-to-physical-unit logical-unit)
		      #x13
		      byte-count
		      disk-address
		      dma-buffer
		      dma-buffer-offset-in-pages
		      nil)
  (wait-for-nupi-command command-block "Tape Write")
  (finish-nupi-command command-block dma-buffer))



(defun nupi-read-from-tape-proceed (command-block logical-unit disk-address byte-count dma-buffer dma-buffer-offset-in-pages)
  (start-nupi-command command-block
		      (nupi-logical-to-physical-unit logical-unit)
		      #x12
		      byte-count
		      disk-address
		      dma-buffer
		      dma-buffer-offset-in-pages
		      t)
  )

(defun nupi-write-to-tape-proceed (command-block logical-unit disk-address byte-count dma-buffer dma-buffer-offset-in-pages)
  (start-nupi-command command-block
		      (nupi-logical-to-physical-unit logical-unit)
		      #x13
		      byte-count
		      disk-address
		      dma-buffer
		      dma-buffer-offset-in-pages
		      nil))


(defun streamer-tape-request-complete (command-block command-name)
  (process-wait command-name #'NuPI-Command-Complete-P command-block)
  (NuPI-Check-Status command-block)
  (finish-nupi-command command-block nil)
  )
))

; From file S2: >Lambda-3>TAPE>NUPI-DEVICE.LISP.28 at 7-Jul-86 10:20:43
#10R TAPE#: 
(COMPILER-LET ((*PACKAGE* (PKG-FIND-PACKAGE "TAPE")))
  (COMPILER::PATCH-SOURCE-FILE "SYS: tape; nupi-device lisp "

;;; -*- Mode:LISP; Package:TAPE; Readtable:CL; Base:10 -*-
;;;
;;; Nupi tape device for explorer.
;;;
;;; -dg 11/26/85
;;;

(defflavor nupi-device ((unit 6)
			(density :ignore)
			(command-block (si:get-nupi-command-block nil)))
	   (basic-tape-device)
  :gettable-instance-variables)


(DefMethod (nupi-device :print-self) (stream ignore slashify)
  (If slashify
      (si:printing-random-object (self stream)
	(format stream "Unit: ~d" unit))
    (format stream "NuPI"))
  )

(defmethod (nupi-device :set-options) (&rest ignore))

(defmethod (nupi-device :deinitialize) ()
  )

(defmethod (nupi-device :lock-device) (&rest ignore))
(defmethod (nupi-device :unlock-device) (&rest ignore))
(defmethod (nupi-device :device-locked-p) (&rest ignore) t)

(defmethod (nupi-device :reset) (&rest ignore)
  (send self :initialize))

(defmethod (nupi-device :status) (&rest ignore))

(defmethod (nupi-device :optimal-chunk-size) (record-size)
  record-size
  )


(defmethod (nupi-device :rewind) (&optional (wait-p t))
  (si:start-nupi-simple-command
    command-block
    (si:nupi-logical-to-physical-unit unit)
    #x20 0 0 nil nil nil)
  (When wait-p
    (si:wait-for-nupi-command command-block "Tape Rewind")
    (si:finish-nupi-command command-block nil))
  )

(defmethod (nupi-device :unload) ()
  (si:simple-nupi-command
    command-block #x21 unit 0 0 nil nil nil "Tape Unload")
  )

(defmethod (nupi-device :speed-threshold) (record-size)
  record-size
  -1)


(defmethod (nupi-device :space) (number-of-records &optional (speed :low))
  (check-type number-of-records (integer 1))
  (check-type speed (member :high :low))
  ;; Convert from LMFL (4096 byte) blocks to NuPI (1024 byte) blocks.
  (Let ((number-of-blocks (/ (* number-of-records 4096.) 1024.)))
    (condition-case ()
	(si:simple-nupi-command
	 command-block #x23 unit 0 4 number-of-blocks 0 nil "Tape Space")
      (tape:filemark-encountered )))
  )



(defmethod (nupi-device :space-reverse) (number-of-records &optional (speed :low))
  (check-type number-of-records (integer 1))
  (check-type speed (member :high :low))
  (signal 'tape:driver-error
	  :device-type 'nupi-device
	  :error-code #x23
	  :error-message "Space Reverse operation not available.")
  )


(defmethod (nupi-device :search-filemark) (number-of-filemarks &optional (speed :low))
  speed ; not used
  (si:simple-nupi-command
    command-block #x27 unit 0 4 number-of-filemarks 0 nil "Tape Search FM")
  )


(defmethod (nupi-device :search-filemark-reverse) (number-of-filemarks &optional (speed :low))
  number-of-filemarks
  speed
  (signal 'tape:driver-error
	  :device-type 'nupi-device
	  :error-code  #x27
	  :error-message "Search Reverse Filemark operation not available.")
  )


(defmethod (nupi-device :read-block) (dma-buffer record-size)
  (setq record-size 4096)
  (si:nupi-read-from-tape command-block unit 0 record-size dma-buffer 0))


(defmethod (nupi-device :write-block) (dma-buffer record-size)
  (si:nupi-write-to-tape command-block unit 0 record-size dma-buffer 0))


(defmethod (nupi-device :read-array) (array number-of-records record-size)
  (check-arg array (memq (array-type array) `(art-8b art-string art-16b art-32b))
			 "an art-8, art-string, art-16b or art-32b array")
  (let* ((nbytes (* record-size number-of-records))
	 (npages (ceiling nbytes (* si:page-size 4))))
    (Condition-Case (error)
	(using-resource (buffer si:dma-buffer npages)
	  (with-buffer-wired (buffer npages)
	    (si:nupi-read-from-tape
	      command-block unit 0 nbytes buffer 0)
	    (case (array-type array)
	      ((art-8b art-string)
	       (si:copy-array-portion
		 (si:dma-buffer-string buffer) 0 nbytes
		 array 0 nbytes))
	      (art-16b
	       (check-arg record-size (zerop (remainder nbytes 2))
			  "a half-word even record-size for an art-16b array")
	       (si:copy-array-portion
		 (si:dma-buffer-16b buffer) 0 (/ nbytes 2)
		 array 0 (/ nbytes 2)))
	      (art-32b
	       (if (eq (named-structure-p array) 'si:dma-buffer)
		   (copy-array-portion
		     (si:dma-buffer-string buffer) 0 nbytes
		     (si:dma-buffer-string array) 0 nbytes)
		 (ferror nil "Error... cannot copy art-32b array."))))))
      (tape:physical-end-of-tape
       ;; Handle this error and generate a new with the right data transfer value.
       (signal 'tape:physical-end-of-tape
	       :device-type 'nupi-device
	       :unit unit
	       :data-transferred (truncate (Send error :data-transferred) record-size)))))
  )


(defmethod (nupi-device :write-array) (array number-of-records record-size)
  (check-arg array (memq (array-type array) `(art-8b art-string art-16b art-32b))
	     "an art-8, art-string, art-16b or art-32b array")
  (let* ((nbytes (* record-size number-of-records))
	 (npages (ceiling nbytes (* si:page-size 4))))
    (Condition-Case (error)
	(using-resource (buffer si:dma-buffer npages)
	  (case (array-type array)
	    ((art-8b art-string)
	     (si:copy-array-portion
	       array 0 nbytes
	       (si:dma-buffer-string buffer) 0 nbytes))
	    (art-16b
	     (check-arg record-size (zerop (remainder nbytes 2))
			"a half-word even record-size for an art-16b array")
	     (si:copy-array-portion
	       array 0 (/ nbytes 2)
	       (si:dma-buffer-16b buffer) 0 (/ nbytes 2)))
	    (art-32b
	     (if (eq (named-structure-p array) 'si:dma-buffer)
		 (copy-array-portion
		   (si:dma-buffer-string array) 0 nbytes
		   (si:dma-buffer-string buffer) 0 nbytes)
	       (ferror nil "Error... cannot copy art-32b array."))))
	  (with-buffer-wired (buffer npages)
	    (si:nupi-write-to-tape
	      command-block unit 0 nbytes buffer 0)))
      (tape:physical-end-of-tape
       ;; Handle this error and generate a new with the right data transfer value.
       (signal 'tape:physical-end-of-tape
	       :device-type 'nupi-device
	       :unit unit
	       :data-transferred (truncate (Send error :data-transferred) record-size)))))
    )


(Defun Report-Progress (old-progress-count block-count granularity)
  (Let ((progress-count (truncate block-count granularity)))
    (When (> progress-count old-progress-count)
      (Format t " ~d" progress-count))
    progress-count)
  )


(defconst *block-transfer-size* 20.)
(defconst *streaming-priority* 20.)


(Defun streamer-tape-block-request-complete (tape-command-block blocks-transferred command-name)
  (Condition-Case (error)
      (si:streamer-tape-request-complete tape-command-block command-name)
    (tape:physical-end-of-tape
     ;; Handle this error and generate a new with the right data transfer value.
     (signal 'tape:physical-end-of-tape
	     :device-type 'nupi-device
	     :unit (ldb (byte 8 0) (si:nupi-command-word tape-command-block))
	     :data-transferred (+ blocks-transferred
				  (truncate (Send error :data-transferred) 1024.)))))
  )


(defmethod (nupi-device :write-from-disk)
	   (disk-unit starting-block number-of-blocks record-size &key silent)
  (check-arg record-size (zerop (remainder record-size (* si:page-size 4)))
	     "a page-even record-size")
  (let* ((chunk-size (min *block-transfer-size* number-of-blocks))
	 (old-priority (Send si:current-process :priority))
	 number-of-chunks
	 last-chunk-size)
    (multiple-value-bind (a b)
	(floor number-of-blocks chunk-size)
      (setq number-of-chunks (if (zerop b) a (add1 a))
	    last-chunk-size (if (zerop b) chunk-size b)))
    (Unless silent
      (Format t "~&Writing ~d blocks to tape (starting at ~d.) Counting hundreds:"
	      number-of-blocks starting-block))
    (Unwind-Protect
	(using-resource (buffer-1 si:dma-buffer chunk-size)
	  (using-resource (buffer-2 si:dma-buffer chunk-size)
	    (using-resource (disk-command-block si:dma-buffer 1)
	      (setf (si:dma-buffer-named-structure-symbol disk-command-block) 'si:nupi-command-block)
	      (using-resource (tape-command-block si:dma-buffer 1)
		(setf (si:dma-buffer-named-structure-symbol tape-command-block) 'si:nupi-command-block)
		(Let ((transfer-size (* chunk-size si:page-size 4))
		      (address starting-block))
		  (Send si:current-process :Set-Priority *streaming-priority*)
		  (si:nupi-read-from-disk
		    disk-command-block disk-unit address transfer-size buffer-1 0)
		  (si:nupi-write-to-tape-proceed
		    tape-command-block unit 0 transfer-size buffer-1 0)
		  (do ((count 1 (add1 count))
		       (progress-count 0)
		       (last-transfer-size chunk-size)
		       (buffer-1? t (not buffer-1?)))
		      ((> count number-of-chunks)
		       ;; Wait for last tape command to complete.
		       (streamer-tape-block-request-complete tape-command-block
			 (- address starting-block last-transfer-size) "Tape Write"))
		    (incf address chunk-size)
		    (When (= count number-of-chunks)
		      (Setq chunk-size last-chunk-size
			    transfer-size (* chunk-size si:page-size 4)))
		    ;; Read into buffer not used by tape request.
		    (si:nupi-read-from-disk
		      disk-command-block disk-unit address transfer-size
		      (If buffer-1? buffer-2 buffer-1) 0)
		    ;; Wait for tape request to complete.
		    (streamer-tape-block-request-complete tape-command-block
		      (- address starting-block last-transfer-size) "Tape Write")
		    ;; Start next tape request.
		    (si:nupi-write-to-tape-proceed
		      tape-command-block unit 0 transfer-size (If buffer-1? buffer-2 buffer-1) 0)
		    (setq last-transfer-size chunk-size)
		    (Unless silent
		      (Setq progress-count
			    (Report-Progress progress-count (- address starting-block) 100.)))))))))
      (Send si:current-process :Set-Priority old-priority)))
  )


(Defun Compare-Buffers (buffer-a buffer-b size)
  (let ((alphabetic-case-affects-string-comparison t))
    (Unless (%string-equal buffer-a 0 buffer-b 0 size)
      (string-compare buffer-a buffer-b)))
  )

(Defun Buffer-Compare (buffer-a buffer-b size block-number address error-list)
  (Let ((compare
	  (compare-buffers
	    (si:dma-buffer-string buffer-a) (si:dma-buffer-string buffer-b) size)))
    (If (Null compare)
	error-list
      (format *error-output* "~& Error in record number ~d; Byte: ~d"
	      block-number (abs compare))
      (cons (cons block-number address) error-list)))
  )


(defmethod (nupi-device :compare-to-disk)
	   (disk-unit starting-block number-of-blocks record-size &key silent)
  (check-unit disk-unit)
  (check-type starting-block (integer 0))
  (check-type number-of-blocks (integer 0))
  (check-type record-size (integer 1))
  (let* ((chunk-size (min *block-transfer-size* number-of-blocks))
	 (old-priority (Send si:current-process :priority))
	 number-of-chunks
	 last-chunk-size
	 error-list)
    (multiple-value-bind (a b)
	(floor number-of-blocks chunk-size)
      (setq number-of-chunks (if (zerop b) a (add1 a))
	    last-chunk-size (if (zerop b) chunk-size b)))
    (Unless silent
      (Format t "~&Comparing ~d blocks to disk. Counting hundreds:" number-of-blocks))
    (unwind-protect
     (using-resource (tape-buffer-1 si:dma-buffer chunk-size)
      (using-resource (tape-buffer-2 si:dma-buffer chunk-size)
       (using-resource (disk-buffer si:dma-buffer chunk-size)
	(using-resource (disk-command-block si:dma-buffer 1)
	  (setf (si:dma-buffer-named-structure-symbol disk-command-block) 'si:nupi-command-block)
	  (using-resource (tape-command-block si:dma-buffer 1)
	    (setf (si:dma-buffer-named-structure-symbol tape-command-block) 'si:nupi-command-block)
	    (Let ((transfer-size (* chunk-size si:page-size 4))
		  (last-transfer-size 0)
		  (address starting-block))
	      (Send si:current-process :Set-Priority *streaming-priority*)
	      (si:nupi-read-from-tape-proceed
		tape-command-block unit 0 transfer-size tape-buffer-1 0)
	      (do ((count 1 (add1 count))
		   (progress-count 0)
		   (buffer-1? t (not buffer-1?)))
		  ((>= count number-of-chunks)
		   (si:nupi-read-from-disk
		     disk-command-block disk-unit address transfer-size disk-buffer 0)
		   (streamer-tape-block-request-complete
		     tape-command-block (- address starting-block) "Tape Read")
		   (Setq error-list
			 (buffer-compare
			   disk-buffer
			   (if buffer-1? tape-buffer-1 tape-buffer-2)
			   transfer-size
			   (* count chunk-size)
			   address
			   error-list)))
		(si:nupi-read-from-disk
		  disk-command-block disk-unit address transfer-size disk-buffer 0)
		(Setq last-transfer-size transfer-size)

		;; Wait for tape request to complete.
		(streamer-tape-block-request-complete
		  tape-command-block (- address starting-block) "Tape Read")

		;; Start next tape read request (into other buffer.)
		(When (= count number-of-chunks)
		  (Setq chunk-size last-chunk-size
			transfer-size (* chunk-size si:page-size 4)))
		(si:nupi-read-from-tape-proceed
		  tape-command-block unit 0 transfer-size (If buffer-1? tape-buffer-2 tape-buffer-1) 0)

		(Setq error-list
		      (buffer-compare
			disk-buffer
			(if buffer-1? tape-buffer-1 tape-buffer-2)
			last-transfer-size
			(* count chunk-size)
			address
			error-list))
		(Unless silent
		  (Setq progress-count (Report-Progress progress-count (- address starting-block) 100.)))
		(incf address chunk-size))))))))
      (Send si:current-process :Set-Priority old-priority))
    (Unless (Null error-list)
      (nreverse error-list) t))
  )

(defmethod (nupi-device :read-to-disk)
	   (disk-unit starting-block number-of-blocks record-size &key silent)
  (check-arg record-size (zerop (remainder record-size (* si:page-size 4)))
	     "a page-even record-size")
  (let* ((chunk-size (min *block-transfer-size* number-of-blocks))
	 (old-priority (Send si:current-process :priority))
	 number-of-chunks
	 last-chunk-size)
    (multiple-value-bind (a b)
	(floor number-of-blocks chunk-size)
      (setq number-of-chunks (if (zerop b) a (add1 a))
	    last-chunk-size (if (zerop b) chunk-size b)))
    (Unless silent
      (Format t "~&Reading ~d blocks from tape. Counting hundreds:" number-of-blocks))
    (Unwind-Protect
	(using-resource (buffer-1 si:dma-buffer chunk-size)
	  (using-resource (buffer-2 si:dma-buffer chunk-size)
	    (using-resource (disk-command-block si:dma-buffer 1)
	      (setf (si:dma-buffer-named-structure-symbol disk-command-block) 'si:nupi-command-block)
	      (using-resource (tape-command-block si:dma-buffer 1)
		(setf (si:dma-buffer-named-structure-symbol tape-command-block) 'si:nupi-command-block)
		(Let ((transfer-size (* chunk-size si:page-size 4))
		      (address starting-block))
		  (Send si:current-process :Set-Priority *streaming-priority*)
		  (si:nupi-read-from-tape-proceed
		    tape-command-block unit 0 transfer-size buffer-1 0)
		  (do ((count 1 (add1 count))
		       (progress-count 0)
		       (buffer-1? t (not buffer-1?)))
		      ((> count number-of-chunks)
		       ;; Wait for last tape request to complete.
		       (streamer-tape-block-request-complete
			 tape-command-block (- address starting-block) "Tape Read")
		       (si:nupi-write-to-disk
			 disk-command-block disk-unit address transfer-size
			 (If buffer-1? buffer-1 buffer-2) 0))
		    ;; Wait for tape request to complete.
		    (streamer-tape-block-request-complete
		      tape-command-block (- address starting-block) "Tape Read")
		    (When (= count number-of-chunks)
		      (Setq chunk-size last-chunk-size
			    transfer-size (* chunk-size si:page-size 4)))
		    ;; Start read into other buffer.
		    (si:nupi-read-from-tape-proceed
		      tape-command-block unit 0 transfer-size (If buffer-1? buffer-2 buffer-1) 0)
		    ;; Write completed buffer to disk.
		    (si:nupi-write-to-disk
		      disk-command-block disk-unit address transfer-size
		      (If buffer-1? buffer-1 buffer-2) 0)
		    (incf address chunk-size)
		    (Unless silent
		      (Setq progress-count (Report-Progress progress-count (- address starting-block) 100.)))))))))
      (Send si:current-process :Set-Priority old-priority)))
  )


(defmethod (nupi-device :write-filemark) (&optional (number-of-marks 1))
  (dotimes (j number-of-marks)
    (si:simple-nupi-command
      command-block #x25 unit 0 0 nil nil nil "Tape Write FM")))


(defmethod (nupi-device :erase-tape) ()
  (si:simple-nupi-command
    command-block #x22 unit 0 0 nil nil nil "Tape Erase")
  )



(defmethod (nupi-device :initialize) (&rest ignore)
  ;; BUFFERED       -  set mode to stream (tells formatter to use buffered vs. unbuffered mode).
  ;; SPEED          -  1 = low, 0 & 2 = high, 3 = auto-adjust
  ;; DENSITY        -  0 formatter defaults (Explorer default), 4 QIC-11 (Symbolics & NuMachine)
  ;;                   6 1/2 inch 3200 bpi,  1-3 variations of 1/2 inch
  ;; BLOCK-SIZE     -  Size in bytes of blocks on tape, (24 bit field).
  ;; This command is send to the formatter (controller) rather than to the tape unit itself.
  (let ((buffered T)
	(speed 0)
	(density-code 0)
	(block-size #x200)
	(long-erase T)
	(unload-retension nil)
	(load-retension nil)
	(parms (* 2 (1+ si:%nupi-reserved-b)))
	(buffer (si:dma-buffer-16b command-block)))
    ;; This next word in the parameter block is RESERVED in SCSI spec but NUPI uses it!!
    (aset 12. buffer parms)   ; scsi data length.
    (let ((half-word (dpb (if long-erase 0 1) #o1701	        ; Info for NUPI only.  Not part of 
			  (dpb (if unload-retension 1 0) #o1601	; this SCSI command!!!
			       (dpb (if load-retension 1 0) #o1501 0)))))
      (aset half-word buffer (+ parms 1)))
    ;; From here down we're stuffing params strictly according to SCSI
    ;; halfword    -     SCSI bytes in parameter list
    ;;    3                   2,3
    ;; halfword    -     SCSI bytes in descriptor block
    ;;    4                   0,1    density-code, number-of-blocks MSB
    ;;    5                   2,3    number-of-blocks  (middle, LSB)
    ;;    6                   4,5    reserved, Block Size MSB
    ;;    7                   6,7    Block Size (middle, LSB)
    (aset #x0000 buffer (+ parms 2))
    (let ((half (dpb (if buffered 1 0) #o0401 #x0800)))	 ; 8 is constant=bytes left (block descriptor)
      (aset (dpb speed #o0002 half) buffer (+ parms 3)))
    (aset (dpb density-code #o0004 0) buffer (+ parms 4))
    (aset (dpb (ldb #o2010 block-size) #o1010 0) buffer (+ parms 6))  ; MSB
    (aset (dpb (ldb #o0010 block-size) #o1010	   ; middle & LSB
	       (dpb (ldb #o1010 block-size) #o0010 0))
	  buffer (+ parms 7))
    ;; Send formatter setup command. +++ dma-buffer parameter is just a place holder +++
    (si:simple-nupi-command
      command-block #x41 unit 0 16. command-block 0 nil "Tape Initialize"))
  )


(defun nupi-device-present? ()
  (select-processor
    (:explorer t)
    (:cadr)
    (:lambda)))

(define-tape-device nupi-device "nt" nupi-device-present?)

))

; From file S2: >Lambda-3>TAPE>lmfl-format.LISP.194 at 7-Jul-86 10:20:51
#10R TAPE#: 
(COMPILER-LET ((*PACKAGE* (PKG-FIND-PACKAGE "TAPE")))
  (COMPILER::PATCH-SOURCE-FILE "SYS: tape; LMFL-FORMAT lisp "

;;; -*- Mode:LISP; Package:TAPE; Readtable:CL; Base:10 -*-
;;
;; Copyright LISP Machine, Inc. 1986
;;   See filename "Copyright" for
;; licensing and release information.
;;;
;;; Support for the LMFL Format
;;;
;;; -dg 9/16/85
;;; -wbg 6/86
;;;

(defconstant *lmfl-default-record-size* 4096)

(defconstant *bytes-per-page* (* si:page-size 4))

(Defun Get-Next-Tape (message format device)
  (terpri) (princ message)
  (send device :rewind)
  (send device :unload)
  (prompt-for-new-tape format device)
  )

;;;
;;; LMFL format flavor definition.
;;;

(defflavor lmfl-format ((record-size)
			(file-stream)
			(tape-modified)
			(current-plist))
	   (basic-tape-format)
  (:gettable-instance-variables))


(defmethod (lmfl-format :initialize) (&rest init-options)
  (check-attribute-list init-options)
  (unless record-size
    (setq record-size *lmfl-default-record-size*))
  (when init-options
    (lexpr-send self :set-options init-options)))


(defmethod (lmfl-format :set-options) (&rest options)
  (check-attribute-list options)
  (if options
      (do* ((l options (cddr l))
	    (option (car l) (car l))
	    (value (cadr l) (cadr l)))
	   ((null l))
	(case option
	  (:record-size
	   (check-type value (integer 1024 #.(* 64 1024)))
	   (check-arg value (zerop (remainder value 1024))
		      "a multiple of 1024 bytes")
	   (setq record-size value))
	  (t (signal 'invalid-option :object self :option option :value value))))
    (tv:choose-variable-values
      `((,(locf record-size) "Record Size" :number))
      :label '(:string "Options for the LMFL tape format" :font fonts:tr12b))))


(defmethod (lmfl-format :read-tape-header) (&rest ignore))
(defmethod (lmfl-format :write-tape-header) (&rest ignore))


(defmethod (lmfl-format :tape-is-your-format-p) (device)
  (check-device device)
  (send self :rewind device t)
  (prog1
    (using-resource (header-block si:dma-buffer (/ record-size si:page-size 4))
      (send device :read-block header-block record-size)
      (string-equal "LMFL" (si:dma-buffer-string header-block) :end2 4))
    (send self :rewind device)))


(defmethod (lmfl-format :space-to-end-of-this-file) (device plist records-passed)
  (check-device device)
  (check-plist plist)
  (check-type records-passed (or null (integer 0)))
  (do* ((total-records (add1 (ceiling (if (get plist :partition)
					  (* (get plist :size) si:page-size 4)
					(* (or (get plist :length-in-bytes)
					       (get plist :length)
					       (ferror nil "length-in-bytes is NIL!"))
					   (/ (file-byte-size plist) 8)))
				      record-size)))
	(passed records-passed)
	(records-to-space-over (- total-records passed) (- total-records passed)))
       ((condition-case (condition)
	    (progn
	     (Setq current-plist nil)
	     (send device :space records-to-space-over
		   (if (> (* records-to-space-over record-size)
			  (send device :speed-threshold record-size))
		       :high
		     :low))
	     t)
	  (physical-end-of-tape
	   (Let ((data-transferred (send condition :data-transferred)))
	     (if (= data-transferred (sub1 records-to-space-over))
		 ;;if end of tape is where filemark would be ignore it for now
		 t
	       (format t "~&File continued on next tape.  Unloading this tape.")
	       (send device :rewind)
	       (send device :unload)
	       (send self :find-continuation-tape device plist)
	       (incf passed data-transferred)
	       nil))))))
  )


(defmethod (lmfl-format :read-file-header) (device &optional (host-for-parsing si:local-host))
  (check-device device)
  (check-host host-for-parsing)
  (If (Not (Null current-plist))
      current-plist
    (let ((*read-base* 10.))
      (using-resource (header-block si:dma-buffer (/ record-size si:page-size 4))
	(condition-case ()
	    (send device :read-block header-block record-size)
	  ((filemark-encountered physical-end-of-tape)
	   (signal 'logical-end-of-tape :device-object device))
	  (:no-error
	   (let* ((string (si:dma-buffer-string header-block))
		  plist)
	     (unless (string-equal string "LMFL" :end1 4)
	       (signal 'bad-file-header
		       :format-type 'lmfl
		       :header string))
	     (setq plist (read-from-string string nil :no-plist :start 4))
	     (if (atom (cdr plist))
		 (signal 'bad-file-header
			 :format-type 'lmfl
			 :header string)
	       (Setq current-plist
		     (cons (when host-for-parsing
			     (fs:make-pathname :host host-for-parsing
					       :device (getf plist :device)
					       :directory (getf plist :directory)
					       :name (getf plist :name)
					       :type (getf plist :type)
					       :version (getf plist :version)))
			   (check-plist-validity
			     (dolist (elem '(:directory :device :name :type :version) plist)
			       (remf plist elem))))))))))))
  )


(defmethod (lmfl-format :restore-partition) (plist device silent)
  (check-plist plist)
  (check-device device)
  (let ((size (get plist :size))
	(comment (or (get plist :comment) (get plist :name))))
    (multiple-value-bind (host unit start ignore ignore name)
	(When (yes-or-no-p "Restore Partition ~s? " comment)
	  (partition-searcher (format nil "for writing partition ~a" comment) size
	     :confirm-write t :default-unit tframe:*default-disk-unit*)) ;; +++
      (unwind-protect
	  (if (null host)
	      (progn 
		(format t "~&*** User Aborted restoring partition: ~A ***" comment)
		(send self :space-to-end-of-this-file device plist 0))
	    (si:update-partition-comment name "Incomplete Copy" unit)
	    (do ((first-block start)
		 (blocks size)
		 finished?)
		(finished?)
	      (Setq current-plist nil)
	      (condition-case (condition)
		  (send device :read-to-disk unit first-block blocks record-size :silent silent)
		(physical-end-of-tape
		 (Get-Next-Tape "Partition continued on another tape.  Unloading..." self device)
		 (incf first-block (send condition :data-transferred))
		 (decf blocks (send condition :data-transferred)))
		(:no-error
		 (si:update-partition-comment name (or (get plist :comment) "??? from tape") unit)
		 (condition-case (condition)
		     (send device :space 1)
		   (physical-end-of-tape))
		 (setq finished? t)))))
	(when unit (si:dispose-of-unit unit))))))


(defmethod (lmfl-format :restore-file) (device &key transform
					(overwrite :never)
					query
					(create-directory :always)
					silent)
  (check-device device)
  (check-type transform (or string pathname compiled-function closure symbol))
  (check-type overwrite (member :query :never :always))
  (check-type create-directory (member :query :never :always :error))
  (let ((chunk-size (floor (send device :optimal-chunk-size record-size)
			   *bytes-per-page*))
	(plist (send self :read-file-header device si:local-host)))
    (if (get plist :partition)
	(send self :restore-partition plist device silent)
      (let* ((byte-size (file-byte-size plist))
	     (length-in-bytes (or (get plist :length-in-bytes)
				  (get plist :length)))
	     (pathname (determine-restore-file-pathname
			 plist transform overwrite query create-directory silent)))
	(if (null pathname)
	    (send self :space-to-end-of-this-file device plist 0)
	  (with-open-file (outstream pathname
				     :direction :output
				     :byte-size byte-size
				     :characters (get plist :characters))
	    (dolist (prop '(:length-in-blocks :length-in-bytes :length
					      :byte-size :not-backed-up :characters))
	      (remprop plist prop))
	    (lexpr-send outstream :change-properties pathname (cdr plist))
	    (using-resource (buffer si:dma-buffer chunk-size)
	      (do* ((chunk-size-in-bytes (* chunk-size si:page-size (/ 32 byte-size)))
		    (record-size-in-bytes (/ record-size (/ byte-size 8)))
		    (buffer-array (case byte-size
				    (8 (si:dma-buffer-string buffer))
				    (16 (si:dma-buffer-16b buffer))))
		    (bytes-to-go length-in-bytes)
		    (bytes-this-transfer (min bytes-to-go chunk-size-in-bytes)
					 (min bytes-to-go chunk-size-in-bytes)))
		   ((zerop bytes-to-go))
		(Setq current-plist nil)
		(condition-case (condition)
		    (send device :read-array
			  buffer-array
			  (ceiling bytes-this-transfer record-size-in-bytes)
			  record-size)
		  (physical-end-of-tape
		   (let* ((data-transferred (send condition :data-transferred))
			  (bytes-left (- bytes-this-transfer (* data-transferred record-size))))
		     (send outstream :string-out buffer-array 0 (* data-transferred record-size))
		     (tv:beep)
		     (format t "~&Tape continued on another tape.  Unloading this tape.")
		     (send device :rewind)
		     (send device :unload)
		     (send self :find-continuation-tape device plist)
		     (send device :read-array
			   buffer-array
			   (ceiling bytes-left record-size)
			   record-size)
		     (send outstream :string-out buffer-array 0 bytes-left)))
		   (:no-error
		     (send outstream :string-out buffer-array 0 bytes-this-transfer)))
		(decf bytes-to-go bytes-this-transfer)))
	    (Setq current-plist nil)
	    (condition-case (condition)
		(send device :space 1)
	      (physical-end-of-tape))))))))


(defmethod (lmfl-format :write-file-header) (device truename attribute-list)
  (check-device device)
  (check-type truename pathname)
  (check-attribute-list attribute-list)
  (let* ((*print-base* 10.)
	 (plist (nconc (unless (getf attribute-list :partition)
			 (list :device (pathname-device truename)
			       :directory (pathname-directory truename)
			       :name (pathname-name truename)
			       :type (pathname-type truename)
			       :version (pathname-version truename)))
		       attribute-list))
	 (string (format nil "LMFL~S" plist)))
    (using-resource (header-block si:dma-buffer (/ record-size si:page-size 4))
      (copy-array-contents string (si:dma-buffer-string header-block))
      (Setq current-plist nil)
      (send device :write-block header-block record-size)
      (setq tape-modified t))))


(defmethod (lmfl-format :write-partition) (partition-name device unit-arg &key
					   silent end start)
  (check-type partition-name string)
  (check-type start (or null (integer 0)))
  (check-type end (or (integer 0) (member t nil)))
  (check-device device)
  (check-type unit-arg (or (integer 0) string closure))
  (si:with-decoded-disk-unit (unit unit-arg "for reading partition")
    (multiple-value-bind (beg length nil name)
	(si:find-disk-partition partition-name nil unit)
      (unless beg
	(ferror 'no-such-partition
		:host (unit-host unit)
		:disk-unit (unit-number unit)
		:partition partition-name))
      (setq start (or start beg)
	    end (cond ((null end)
		       (+ (or (si:measured-from-part-size unit name beg length) length) start))
		      ((integerp end) (+ start end))
		      (t (+ beg length))))
      (unless (and (< start end)
		   (>= start beg)
		   (<= end (+ beg length)))
	(ferror nil "Partition start or end specifications out of bounds."))
      (Setq current-plist nil)
      (using-resource (buffer si:dma-buffer (/ record-size si:page-size 4))
	(let ((*print-base* 10.)
	      (plist (list :partition t :name name :size (- end start)
			   :comment (si:partition-comment name unit)
			   :byte-size 16.
			   :host (unit-host unit) :host-unit (unit-number unit)
			   :creation-date (time:get-universal-time))))
	  (copy-array-contents
	    (format nil "LMFL~s" plist)
	    (si:dma-buffer-string buffer))
	  (setq tape-modified t)
	  (with-device-locked device
	    (send device :write-block buffer record-size)))
	(do ((addr start)
	     (blocks-to-write (- end start)))
	    ((zerop blocks-to-write))
	  (with-device-locked device
	    (condition-case (condition)
		(Progn
		 (setq tape-modified t)
		 (send device :write-from-disk
		       unit addr blocks-to-write record-size :silent silent))
	      (physical-end-of-tape
	       (setq tape-modified nil)
	       (Get-Next-Tape "End of tape during partition. Unloading tape..." self device)
	       (Let ((data-transferred (send condition :data-transferred)))
		 (Incf addr data-transferred)
		 (Decf blocks-to-write data-transferred)))
	      (:no-error
	       (setq blocks-to-write 0)
	       (condition-case ()
		   (send device :write-filemark)
		 (physical-end-of-tape))))))))))


(defmethod (lmfl-format :write-file) (device file &key (end-of-tape-action :continue) silent)
  (check-device device)
  (check-type file (or string pathname list))
  (check-type end-of-tape-action (member :continue :error :return))
  (let ((pathname (if (consp file) (fs:parse-pathname (car file)) (fs:parse-pathname file)))
	(properties (if (consp file) (cdr file))))
    (with-open-file (instream pathname :direction :input
			      :characters (or (getf properties :characters) :default))
      (unless properties
	(setq properties (send instream :plist)))
      (block write-file
	(unless silent
	  (format t "~&Writing file: ~a" pathname))
	(Setq current-plist nil)
	(let ((props (check-plist-validity (or properties (send instream :plist))))
	      (byte-factor (/ (file-byte-size instream) 8))
	      (chunk-size (floor (send device :optimal-chunk-size record-size) record-size))
	      (truename (send instream :truename))
	      number-of-records
	      last-record-fill)
	  (condition-case (condition)
	      (send self :write-file-header device truename props)
	    (physical-end-of-tape
	     (ecase end-of-tape-action
	       (:error
		(signal 'end-of-tape-writing-header
		   :file-plist (cons nil props)
		   :device device))
	       (:continue
		(setq tape-modified nil)
		(Get-Next-Tape
		  "Physical end of tape.  Continue on next tape.  Unloading..." self device)
		(send self :write-file-header device truename props))
	       (:return
		(return-from write-file
		  (make-condition 'end-of-tape-writing-header
				  :file-plist (cons nil props)
				  :device device))))))
	  (using-resource (buffer si:dma-buffer (* chunk-size (/ record-size *bytes-per-page*)))
	    (multiple-value-bind (a b)
		(floor (* (send instream :length) byte-factor) record-size)
	      (setq number-of-records (if (zerop b) a (add1 a))
		    last-record-fill (/ (if (zerop b) record-size b) byte-factor)))
	    (do* ((record-count 0)
		  (rs (/ record-size byte-factor))
		  (records-this-pass (min (- number-of-records record-count) chunk-size)
				     (min (- number-of-records record-count) chunk-size))
		  (last-bunch (<= (- number-of-records record-count) chunk-size)
			      (<= (- number-of-records record-count) chunk-size))
		  (buffer-array (ecase byte-factor
				  (1 (si:dma-buffer-8b buffer))
				  (2 (si:dma-buffer-16b buffer)))))
		 ((= record-count number-of-records)
		  (condition-case (condition)
		      (send device :write-filemark)
		    (physical-end-of-tape))
		  t)
	      (send instream :string-in nil buffer-array 0
		    (if (not last-bunch)
			(* records-this-pass rs)
		      ;; stupid format lossage
		      (array-initialize buffer-array 0
					(+ (* (sub1 records-this-pass) rs) last-record-fill)
					(* records-this-pass rs))
		      (+ (* (sub1 records-this-pass) rs) last-record-fill)))
	      (condition-case (condition)
		  (send device :write-array buffer-array records-this-pass record-size)
		(physical-end-of-tape
		 (ecase end-of-tape-action
		   ((:error :return)
		    (let ((cond (make-condition
				 'end-of-tape-writing-file
				   :file-plist (cons nil props)
				   :device device
				   :bytes-transferred
				      (* (+ record-count (send condition :data-transferred)) rs))))
		      (case end-of-tape-action
			(:return (return-from write-file cond))
			(:error (signal cond)))))
		   (:continue
		    (setq tape-modified nil)
		    (Get-Next-Tape
		      "Physical end of tape encountered.  Continue on next tape.  Unloading..." self device)
		    (let ((records-written (send condition :data-transferred)))
		      (format t "~&Writing continuation header...")
		      (send self :write-file-header
			    device
			    (fs:parse-pathname "lm:continuation.file#0")
			    (let ((bytes-left (- (or (send-if-handles instream :length-in-bytes)
						     (send instream :length))
						 (* (+ record-count records-written) rs))))
			      (list :byte-size (file-byte-size props)
				    :length-in-bytes bytes-left
				    :length-in-blocks (ceiling (* bytes-left byte-factor)
							       *bytes-per-page*)
				    :continuation-properties props)))
		      (format t "written.")
		      (using-resource
			  (temp-buffer si:dma-buffer (* (- records-this-pass records-written)
							(/ rs *bytes-per-page*)))
			(copy-array-portion
			  buffer-array (* records-written rs) (* records-this-pass record-size)
			  (case byte-factor
			    (1 (si:dma-buffer-8b temp-buffer))
			    (2 (si:dma-buffer-16b temp-buffer)))
			  0
			  (* (- records-this-pass records-written) rs))
			(format t "~&Writing partial record data...")
			(send device :write-array
			      (si:dma-buffer-8b temp-buffer)
			      (- records-this-pass records-written)
			      record-size)
			(format t "written."))
		      (incf record-count records-this-pass)))))
		(:no-error
		 (incf record-count records-this-pass)))))))))
  )

;;; Need to do a better job than this. (properties may be in a different order, etc.)
(defun continuation-properties-equal (plist cplist)
  (equalp (get cplist :continuation-properties) plist))


(defmethod (lmfl-format :find-continuation-tape) (device plist)
  (check-device device)
  (check-plist plist)
  (do () (nil)
    (prompt-for-new-tape self device)
    (let ((cplist (send self :read-file-header device)))
      (if (continuation-properties-equal plist cplist)
	  (return t)
	(When (yes-or-no-p
		"The current tape does not appear to be a continuation of the last tape.  Use it anyway?")
	      (return t)))))
  )


(defmethod (lmfl-format :compare-partition) (device plist silent)
  (check-device device)
  (check-plist plist)
  (let ((part-disk-unit (get plist :host-unit))
	(part-host (or (condition-case ()
			   (si:parse-host (get plist :host))
			 (si:unknown-host-name))
		       si:local-host))
	(plist-name (get plist :name)))
    (multiple-value-bind (host unit start length nil name)
	(partition-searcher
	  (format nil "for comparing ~s" plist-name)
	  (get plist :size)
	  :default-partition (typecase plist-name
			       ((or string null) plist-name)
			       (t nil))
	  :default-unit (if (eq (si:parse-host part-host) si:local-host)
			    part-disk-unit
			  (format nil "~A ~D" part-host part-disk-unit))
	  :default-comment (get plist :comment))
      (if (null host)
	  (progn (format t "~&*** User aborted comparison of partition: ~a ***"
			 (or (get plist :comment) (get plist :name)))
		 (send self :space-to-end-of-this-file device plist 0))
	(do ((first start)
	     (blocks (or (si:measured-from-part-size unit name start length) length))
	     Result
	     finished?)
	    (finished? result)
	  (condition-case (condition)
	      (setq result (send device :compare-to-disk
				 unit first blocks record-size :silent silent))
	    (physical-end-of-tape
	     (format t "~&Partition continued on another tape.  Unloading this tape...")
	     (send device :rewind)
	     (send device :unload)
	     (prompt-for-new-tape self device)
	     (Let ((data-transferred (send condition :data-transferred)))
	       (incf first data-transferred)
	       (decf blocks data-transferred)))
	    (:no-error
	     (setq finished? t)
	     (if result
		 (condition-case ()
		     (send device :space 1)
		   ((physical-end-of-tape filemark-encountered)
		    (signal 'logical-end-of-tape :device-object device)))
	       (do (finished?)
		   (finished?)
		 (condition-case (condition)
		     (send device :search-filemark 1 :high)
		   (physical-end-of-tape
		    (prompt-for-new-tape self device))
		   (:no-error (setq finished? t))))))))))))


(defmethod (lmfl-format :compare-file) (device &key transform silent (error-action :return))
  (check-device device)
  (let* ((pl (send self :read-file-header device si:local-host))
	 (max-chunk (send device :optimal-chunk-size record-size))
	 (pathname (if transform
		       (process-transform transform pl)
		     (car pl)))
	 (length-in-bytes (or (get pl :length-in-bytes)
			      (get pl :length)
			      (get pl :size)	; for partitions
			      (ferror nil "length in bytes is NIL!")))
	 (byte-factor (/ (file-byte-size pl) 8))
	 number-of-chunks
	 last-chunk-size)
    (setq current-plist nil)
    (if (get pl :partition)
	(send self :compare-partition device pl silent)
      (if (not (condition-case (cond)
		   (probef pathname)
		 (fs:directory-not-found)))
	  (let ((cond (make-condition 'compare-source-not-found :source-file pathname)))
	    (send self :space-to-end-of-this-file device pl 0)
	    (case error-action
	      (:return
	       (unless silent
		 (format t "~&File \"~a\" not found for comparison" pathname))
	       cond)
	      (:error (signal-condition cond))))
	(block really-compare
	  (multiple-value-bind (a b)
	      (floor (* length-in-bytes byte-factor) max-chunk)
	    (setq number-of-chunks (if (zerop b) a (add1 a))
		  last-chunk-size (if (zerop b) max-chunk b))
	    (with-open-file (f pathname
			       :direction :input
			       :characters :default)
	      (using-resource (fbuffer si:dma-buffer (/ max-chunk *bytes-per-page*))
		(using-resource (tbuffer si:dma-buffer (/ max-chunk *bytes-per-page*))
		  (unless silent
		    (format t "~&Comparing \"~a\" ... " pathname))
		  (unless (and (= length-in-bytes
				  (or (get f :length-in-bytes)
				      (get f :length)
				      (ferror nil "file's length in bytes is NIL!")))
			       (= (file-byte-size pl) (file-byte-size f))
			       (= (get pl :creation-date) (get f :creation-date))
			       (eq (get pl :characters) (get f :characters)))
		    (let ((cond (make-condition 'compare-source-changed
						:source-plist (cons (send f :truename)
								    (plist f))
						:file-plist pl)))
		      (unless silent
			(format t "[*** Not Compared ***]"))
		      (send self :space-to-end-of-this-file device pl 0)
		      (case error-action
			(:return (return-from really-compare cond))
			(:error (signal-condition cond)))))
		  (when (zerop length-in-bytes)
		    (send device :space 1)
		    (Setq current-plist nil)
		    (format t "[Zero Length]")
		    (return-from really-compare pl))
		  (do* ((count 0 (add1 count))
			(records-compared 0)
			(bytes-this-time	;note these are 8-bit bytes
			  (if (= count (sub1 number-of-chunks)) last-chunk-size max-chunk)
			  (if (= count (sub1 number-of-chunks)) last-chunk-size max-chunk))
			(farray (case byte-factor
				  (1 (si:dma-buffer-8b fbuffer))
				  (2 (si:dma-buffer-16b fbuffer))))
			(fstring (si:dma-buffer-string fbuffer))
			(tstring (si:dma-buffer-string tbuffer))
			unequalp)
		       ((or (= count number-of-chunks) unequalp)
			(if unequalp
			    (let ((cond (make-condition 'compare-error
							:source-file (send f :truename)
							:file-plist pl)))
			      (unless silent
				(format t "[*** Unequal ***]"))
			      (ecase error-action
				(:return
				 (send self :space-to-end-of-this-file device pl records-compared)
				 cond)
				(:error (signal-condition cond))))
			  (unless silent
			    (format t "[Equal]"))
			  (Setq current-plist nil)
			  (condition-case (condition)
			      (send device :space 1)
			    (physical-end-of-tape))
			  pl))
		    (send f :string-in nil farray 0 (/ bytes-this-time byte-factor))
		    (Setq current-plist nil)
		    (condition-case (condition)
			(send device :read-array
			      tstring (ceiling bytes-this-time record-size) record-size)
		      (physical-end-of-tape
		       (let* ((records-read (send condition :data-transferred))
			      (bytes-left (- bytes-this-time (* records-read record-size))))
			 (format t "~&File continued on another tape.  Unloading...")
			 (send device :rewind)
			 (send device :unload)
			 (send self :find-continuation-tape device pl)
			 (if (string-not-equal fstring tstring
					       :end1 (- bytes-this-time bytes-left)
					       :end2 (- bytes-this-time bytes-left))
			     (setq unequalp t
				   records-compared (+ records-compared records-read))
			   (send device :read-array
				 tstring (ceiling bytes-left record-size) record-size)
			   (unless (string-equal fstring tstring
						 :Start1 (- bytes-this-time bytes-left)
						 :end1 bytes-this-time
						 :end2 bytes-left)
			     (setq unequalp t))
			   (incf records-compared (ceiling bytes-this-time record-size)))))
		      (:no-error
		       (unless (string-equal fstring tstring
					     :start1 0
					     :end1 bytes-this-time
					     :Start2 0
					     :end2 bytes-this-time)
			 (setq unequalp t
			       records-compared
			       (ceiling bytes-this-time record-size)))))))))))))))


(defmethod (lmfl-format :beginning-of-file) (device)
  (check-device device)
  (Setq current-plist nil)
  (condition-case ()
      (send device :search-filemark-reverse 1 :high)
    (physical-beginning-of-tape)))


(defmethod (lmfl-format :next-file) (device &optional (nfiles 1))
  (check-device device)
  (check-type nfiles (integer 1))
  (Setq current-plist nil)
  (dotimes (c nfiles) 
    (send device :search-filemark 1 :high)))


(defmethod (lmfl-format :previous-file) (device &optional (nfiles 1))
  (check-device device)
  (check-type nfiles (integer 1))
  (send self :beginning-of-file device)
  (dotimes (times nfiles)
    (send device :space-reverse 1)
    (send device :search-filemark-reverse 1 :high)))


(defmethod (lmfl-format :find-file) (device match)
  (check-device device)
  (check-type match (or list compiled-function closure symbol string pathname))
  (do ((pl (send self :read-file-header device si:local-host)
	   (send self :read-file-header device si:local-host)))
      ((tape-file-match match pl)
       pl)
    (send self :space-to-end-of-this-file device pl 0)))


(defmethod (lmfl-format :find-file-reverse) (device match)
  (check-device device)
  (check-type match (or list compiled-function closure symbol string pathname))
  (send self :beginning-of-file device)
  (send self :previous-file device)
  (do ((pl (send self :read-file-header device si:local-host)
	   (send self :read-file-header device si:local-host)))
      ((tape-file-match match pl)
       pl)
    (send self :beginning-of-file device)
    (send self :previous-file device)))


(defmethod (lmfl-format :open-file) (device &key
				     (direction :input)
				     (byte-size :default)
				     (characters :default)
				     plist)
  (check-device device)
  (check-type direction (member :input :output))
  (check-type byte-size (member 8 16 :default))
  (check-type characters (member :default t nil))
  (when (eq direction :output)
    (check-plist plist))
  (when file-stream
    (case (send file-stream :status)
      ((:bof :closed))
      (t (close file-stream :abort t))))
  (when (and (eq direction :output) (null plist))
    (signal 'protocol-violation
	    :format-string "LMFL Output stream must have a plist."))
  (let* ((pl (if (eq direction :input) (send self :read-file-header device) plist))
	 (*characters (if (eq characters :default)
			  (get pl :characters)
			(setf (get plist :characters) characters)))
	 (*byte-size (if (eq byte-size :default)
			 (file-byte-size pl)
		       (setf (get pl :byte-size) byte-size))))
    (send device :lock-device)
    (when (eq direction :output)
      (send self :write-file-header device (car pl) (cdr pl)))
    (setq tape-modified (eq direction :output)
	  file-stream (make-instance (case direction
				       (:input (if (not *characters)
						   'lmfl-input-stream
						 'lmfl-input-character-stream))
				       (:output (if (not *characters)
						    'lmfl-output-stream
						  'lmfl-output-character-stream)))
				     :device device
				     :byte-size *byte-size
				     :record-size record-size
				     :format self
				     :pathname (car pl)
				     :property-list (cdr pl)))))
		 
(defmethod (lmfl-format :list-files) (device &key (stream *standard-output*) (number-of-files -1))
  (check-device device)
  (check-type number-of-files (integer))
  (let (list)
    (condition-case ()
	(do (plist
	     byte-size
	     (count 0 (add1 count)))
	    ((= count number-of-files) (reverse list))
	  (setq plist (send self :read-file-header device)
		byte-size (file-byte-size plist))
	  (push plist list)
	  (when stream
	    (if (get plist :partition)
		(format stream "~&Partition: \"~A\" - Length in Blocks: ~D"
			(or (get plist :comment) "Unknown")
			(get plist :size))
	      (format stream "~&~A ~50TByte Size: ~D ~65T- Length in bytes: ~D"
		      (car plist)
		      byte-size
		      (or (get plist :length-in-bytes)
			  (get plist :length)))))
	  (send self :space-to-end-of-this-file device plist 0))
      (logical-end-of-tape
       (Condition-Case ()
	   (send device :space-reverse 1)
	 (driver-error))
       (reverse list)))))

(defmethod (lmfl-format :finish-tape) (device)
  (check-device device)
  (when tape-modified
    (condition-case ()
	(send device :write-filemark)
      (physical-end-of-tape)
      (:no-error
       (Condition-Case ()
	   (send device :space-reverse 1)
	 (driver-error))))
    (setq tape-modified nil)))

(defmethod (lmfl-format :rewind) (device &optional (wait-p t))
  (when file-stream
    (close file-stream :abort t)
    (setq file-stream nil))
  (when tape-modified
    (case (prompt-for-rewind-with-state)
      (:resume (setq tape-modified nil))
      (:save-state (send self :finish-tape device))
      (:enter-debugger
       (ferror nil "Tape state not saved (debug request by user)"))))
  (Setq current-plist nil)
  (send device :rewind wait-p))

(defmethod (lmfl-format :unload) (device)
  (when file-stream
    (close file-stream :abort t)
    (setq file-stream nil))
  (when tape-modified
    (case (prompt-for-rewind-with-state)
      (:resume (setq tape-modified nil))
      (:save-state (send self :finish-tape device))
      (:enter-debugger
       (ferror nil "Tape state not saved (debug request by user)"))))
  (Setq current-plist nil)
  (send device :unload))

(defmethod (lmfl-format :position-to-append) (device)
  (check-device device)
  (Setq current-plist nil)
  (send device :search-filemark 2 :high)
  (send device :space-reverse 1))

(compile-flavor-methods lmfl-format)

(define-tape-format lmfl-format "lmfl")

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; LMFL File streams
;;;

(defflavor lmfl-input-mixin () (tape-stream-mixin))

(defmethod (lmfl-input-mixin :close) (&optional abort-p)
  (unless (eq status :closed)
    (unless (or abort-p (eq status :eof))
      (send device :search-filemark 1))
    (setq status :closed)
    (when dma-buffer
      (deallocate-resource 'si:dma-buffer dma-buffer))
    (setq dma-buffer nil
	  io-buffer nil)
    (send device :unlock-device)))

(defflavor lmfl-input-character-stream ()
	   (lmfl-input-mixin si:buffered-input-character-stream))

(defflavor lmfl-input-stream ()
	   (lmfl-input-mixin si:buffered-input-stream))

(compile-flavor-methods lmfl-input-character-stream lmfl-input-stream)

;;;;;;;;;;;;;;;;;;;;

(defflavor lmfl-output-mixin () (tape-stream-mixin))

(defmethod (lmfl-output-mixin :close) (&optional abort-p)
  (unless (eq status :closed)
    (setq status :closed)
    (unless abort-p
      (send self :force-output)
      (send device :write-filemark))
    (when dma-buffer
      (deallocate-resource 'si:dma-buffer dma-buffer))
    (setq dma-buffer nil
	  io-buffer nil)
    (send device :unlock-device)))

(defflavor lmfl-output-stream ()
	   (lmfl-output-mixin si:buffered-output-stream))

(defflavor lmfl-output-character-stream ()
	   (lmfl-output-mixin si:buffered-output-character-stream))

(compile-flavor-methods lmfl-output-stream lmfl-output-character-stream)

))
