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

#|

Copyright LISP Machine, Inc. 1985
   See filename "Copyright" for
 licensing and release information.

This answers the question: 
 "How do I access the disk at a lower level of abstraction
  than the file system or virtual memory?"

  2/25/85 15:21:15 -George Carrette


Usage: First, choose a disk partition from: (print-disk-label),
       that is not being used for anything else such as FILE (filesystem),
       LMC (microcode), LOD (lisp saved environments), PAGE (virual memory support),
       METER (the METER:TEST functions). The METER partition is a good one to
       use for testing purposes if you are not otherwise using it.
       If a free partition does not exist then you will have to use SI:EDIT-DISK-LABEL
       to create one, by (for example) changing the name of an unused LOD partition to
       USER, or MINE, etc. Let's say we call this partition "USER".
  
       Then: (initialize-disk-partition "USER" "For my own database")
       You need to do the above only once.

       Then: (setq p (mount-disk-partition "USER" :ACCESS :WRITE))

       Then: (read-disk-block p 3)
         or: (write-disk-block p 3 "a string")
       
More abstract, presumably safer and easier to use routines for hacking
"the disk" at a lower level than the file system allows. This may make
it easier for people to implement their own file systems and databases.

Initialization:
This doesn't do anything but set up the name and comment
and perhaps take optional arguments for zeroing out a partition,
testing for bad blocks etc.

Maybe reserve the first block of the partition for useful information
such to be maintained by the mount/dismount functions about who
initialized and mounted and when etc.

If software testing for bad blocks is implemented then keep a
bitmap of bad-block information which
the READ-BLOCK and WRITE-BLOCK functions can check to prevent people
from losing by using bad blocks.

We could probably do well to abstract further: Each block on disk
with a type code, a reference count. Have a bit map of block usage,
reserved and free. The overhead for this sort of thing is minimal
Use a type borrowed from lisp implementation experience, a BIBOP
like in PDP-10 maclisp allows raw blocks to be very raw indeed. 


We might also want to implement a process-lock on disk partitions.
Note well: There is no INTERPROCESS LOCK at this level.
           The user should handle this himself, by handshaking via
           shared-memory read/write for example.

|#


(defvar *disk-partition-alist* nil)

(defflavor disk-partition (name
			   unit
			   start
			   length
			   (mounted nil)		; NIL, :READ or :WRITE
			   (rqb nil)
			   transfer-size
			   lock
			   wired?)
	   ()
  :ordered-instance-variables
  :outside-accessible-instance-variables
  (:initable-instance-variables unit name))

(defmethod (disk-partition :print-self) (stream &rest ignore)
  (si:printing-random-object (self stream :type)
    (format stream "~A ~:[not ~;~]mounted ~S blocks"
	    name mounted length)))

(add-initialization "Dismount user disk partitions"
		    '(mapc #'(lambda (p)
			       (send (cdr p) :dismount))
			   *disk-partition-alist*)
		    '(:before-cold))

(defmethod (disk-partition :after :init) (ignore)
  (check-type unit (integer 0))
  (push (cons name self) *disk-partition-alist*))

(defun instantiate-disk-partition (partition-name &optional (unit 0))
  (etypecase partition-name
    (symbol (setq partition-name (symbol-name partition-name)))
    (string))
  (unless (SI:FIND-DISK-PARTITION PARTITION-NAME NIL UNIT)
    (ferror "No disk partition named /"~A/"" partition-name))
  (without-interrupts
    (or (cdr (assq partition-name *disk-partition-alist*))
	(make-instance 'disk-partition :name partition-name :unit unit))))

(defun initialize-disk-partition (partition-name partition-comment
				  &key (unit 0)
				       (zero? nil)
				       (check? nil))
  "Initialize a disk partition for later use by the function
MOUNT-DISK-PARTITION which is in turn a prequisite for using
READ-DISK-BLOCK and WRITE-DISK-BLOCK.
Optional arguments specify the zeroing of blocks and the checking for bad-blocks."
  (let ((p (instantiate-disk-partition partition-name unit)))
    (lexpr-send p :initialize partition-name partition-comment :zero? zero? :check? check?)))

(defmethod (disk-partition :initialize) (partition-comment &key (zero? nil)
								(check? nil))
  (check-type partition-comment string)
  (unwind-protect
      (progn (send self :mount :access :write :wired? t)
	     (si:update-partition-comment name
					  partition-comment
					  unit)
	     (if check? (send self :check-for-bad-blocks))
	     (if zero? (send self :zero-contents))
	     self)
    (send self :dismount)))

(defmethod (disk-partition :mount) (&key (access :read) (transfer-size-in-bytes 1024.)
					 ((:wired? wire-me) t))
  (check-type access (member :read :write))
  (if mounted (dismount-disk-partition self))
  (when rqb
    (si:return-disk-rqb
      (prog1 rqb (setq rqb nil))))
  (multiple-value-bind (begin size)
      (funcall (cadr (assq access '((:read si:find-disk-partition-for-read)
				    (:write si:find-disk-partition-for-write))))
	       name
	       nil
	       unit)
    (cond ((null begin)
	   (ferror "Partition not found or access not granted"))
	  ((not (zerop (mod size
			    (floor transfer-size-in-bytes (* 4 si:page-size)))))
	   (ferror "Partition not the right size for transfers of ~D bytes"
		   transfer-size-in-bytes))
	  (t
	   (setf rqb (si:get-disk-rqb (ceiling transfer-size-in-bytes
					       (* 4 si:page-size))))
	   (if (setf wired? wire-me)
	       (si:wire-disk-rqb rqb))
	   (setf start begin)
	   (setf length size)
	   (setf transfer-size transfer-size-in-bytes)
	   (setf mounted (cadr (assq access
				     '((:read (:read))
				       (:write (:read :write))))))
	   self))))

(defun mount-disk-partition (disk-partition-instance &rest options
			     &key (access :read) (transfer-size-in-bytes 1024.)
				  (wired? t))
  access transfer-size-in-bytes wired?
  (check-type disk-partition-instance disk-partition)
  (lexpr-send disk-partition-instance :mount options))

(defmethod (disk-partition :dismount) ()
  (when mounted
    (setf mounted nil)
    (when rqb
      (si:return-disk-rqb
	(prog1 rqb (setq rqb nil)))))
  self)

(defun dismount-disk-partition (disk-partition-instance)
  (check-type disk-partition-instance disk-partition)
  (send disk-partition-instance :dismount))


;;; maintenence and allocation:

(defmacro check-block-arg (part block)
  `(check-arg ,block (and (fixnump ,block)
			  (not (< ,block 0))
			  (not (> ,block (disk-partition-length ,part))))
	      "an integer fitting in the disk partition size"))


(defmethod (disk-partition :zero-contents) (&optional (verbose t))
  (or (memq :write mounted)
      (ferror "~S doesn't support ~A access" self :write))
  (block lose
    (when verbose
      (let ((*standard-output* *error-output*))
	(describe self)
	(unless (yes-or-no-p "Do you REALLY want to zero out ~S" self)
	  (return-from lose nil))))
    (let* ((blockn (floor transfer-size (* 4 si:page-size)))
	   (n-writes (floor length blockn))
	   (peek-every (ceiling n-writes 500.)))
      (when verbose
	(format *error-output*
		"Must do ~D writes. Watch for ~D row~p of dots. One dot = ~D write~p~%"
		n-writes
		(ceiling n-writes (* peek-every 50.))
		(ceiling n-writes (* peek-every 50.))
		peek-every))
      (fill (si:rqb-buffer rqb) 0)
      (dotimes (j n-writes)
	(send self :write-block (* j blockn) "")
	(when verbose
	  (if (zerop (mod j peek-every)) (princ "."))
	  (if (zerop (mod (1+ j) (* peek-every 50.))) (terpri *error-output*)))))))

(defmethod (disk-partition :check-for-bad-blocks) ()
  (cerror "Ignore the attempt to do checking" "~&Checking of ~S is unimplemented~%" self))


;;; READ and WRITE:

(defun read-disk-block (part block &optional into)
  "Reads block BLOCK from the partition and copies the data into string INTO"
  (check-type part disk-partition)
  (send part :read-block block into))

(defmethod (disk-partition :read-block) (block &optional into &aux temp)
  (or (memq :read mounted)
      (ferror "~S doesn't support ~A access" self :read))
  (check-block-arg self block)
  (cond ((not wired?)
	 (si:disk-read rqb
		       unit
		       (+ start block)))
	((and (setq temp (si::%page-status rqb))
	      (= (ldb si::%%PHT1-SWAP-STATUS-CODE temp) si::%PHT-SWAP-STATUS-WIRED))
	 (si:disk-read-wired rqb
			     unit
			     (+ start block)))
	(t
	 (ferror "Internal inconsistency: disk rqb not wired when expected")))
  (unless into
    (setq into (make-string transfer-size)))
  (copy-array-contents (si:rqb-8-bit-buffer rqb) into)
  into)

;>> WTF?!
;(defsetf read-disk-block write-disk-block)

(defun write-disk-block (part block value)
  "Write the string VALUE into the block BLOCK inside the partition PART"
  (check-type part disk-partition)
  (send part :write-block block value))

(defmethod (disk-partition :write-block) (block from &aux tem)
  (or (memq :write mounted)
      (ferror "~S doesn't support ~A access" self :write))
  (check-block-arg self block)
  (unless (and (arrayp from) (eq (array-type from) 'art-8b))
    (check-type from string))
  (copy-array-contents from (si:rqb-8-bit-buffer rqb))
  (cond ((not wired?)
	 (si:disk-write rqb
			unit
			(+ start block)))
	((and (setq tem (si::%page-status rqb))
	      (= (ldb si::%%PHT1-SWAP-STATUS-CODE tem) si::%PHT-SWAP-STATUS-WIRED))
	 (si:disk-write-wired rqb
			      unit
			      (+ start block)))
	(t
	 (ferror "Internal inconsistency: disk rqb not wired when expected"))))



;;;; now streams for partitions

(defun make-partition-input-stream (&key partition-name
					 (byte-size 8)
					 (unit 0))
  (make-instance 'partition-input-stream
		 :partition-name partition-name
		 :byte-size byte-size
		 :unit unit))

(defflavor partition-input-stream
	   ((byte-size 8.)
	    user-elements-per-page
	    partition
	    (next-page-number 0)
	    array-as-characters
	    array-for-user)
	   (si:input-pointer-remembering-mixin
	    si:buffered-input-stream)
  :inittable-instance-variables
  (:init-keywords :partition-name :unit)
  (:required-init-keywords :partition-name))

(defmethod (partition-input-stream :after :init) (init-plist)
  (check-type byte-size (member 8. 16.) "byte-size must be either 8 or 16 bits")
  (setq user-elements-per-page (case byte-size
				 (8. (* page-size 4))
				 (16. (* page-size 2))))
  (setq partition (instantiate-disk-partition
		    (get init-plist :partition-name)
		    (get init-plist :unit 0)))
  (mount-disk-partition partition :wired? nil)
  (setq array-for-user (make-array user-elements-per-page
				   :type (case byte-size
					   (8. 'art-8b)
					   (16. 'art-16b))))
  (setq array-as-characters (make-string (* page-size 4)
					 :displaced-to array-for-user)))

(defmethod (partition-input-stream :next-input-buffer) (no-hang-p)
  (declare (values array start end)
	   (ignore no-hang-p))
  (cond ((= next-page-number (disk-partition-length partition))
	 ())
	('else
	 (read-disk-block partition next-page-number array-as-characters)
	 (incf next-page-number)
	 (values array-for-user 0 user-elements-per-page))))

(defmethod (partition-input-stream :length) ()
  (* user-elements-per-page (disk-partition-length partition)))

(defmethod (partition-input-stream :discard-input-buffer) (ignore)
  nil)

(defmethod (partition-input-stream :set-buffer-pointer) (new-pointer)
  (setq next-page-number (floor new-pointer user-elements-per-page))
  (* next-page-number user-elements-per-page))

(defmethod (partition-input-stream :close) (&optional abortp)
  (declare (ignore abortp))
  (dismount-disk-partition partition))


;;; an output stream.   ******* NOT YET TESTED ******* -naha
;;; just great, we all love untested code in system sources.
;;; now test -gjc

(defun make-partition-output-stream (&key partition-name
					 (byte-size 8)
					 (unit 0))
  (make-instance 'partition-output-stream
		 :partition-name partition-name
		 :byte-size byte-size
		 :unit unit))

(defflavor partition-output-stream
	   ((byte-size 8.)
	    user-elements-per-page
	    partition
	    (next-page-number 0)
	    array-as-characters
	    array-for-user)
	   (si:output-pointer-remembering-mixin
	    si:buffered-output-stream)
  :inittable-instance-variables
  :gettable-instance-variables
  (:init-keywords :partition-name :unit)
  (:required-init-keywords :partition-name))

(defmethod (partition-output-stream :after :init) (init-plist)
  (check-type byte-size (member 8. 16.) "byte-size must be either 8 or 16 bits")
  (setq user-elements-per-page (case byte-size
				 (8. (* page-size 4))
				 (16. (* page-size 2))))
  (setq partition (instantiate-disk-partition
		    (get init-plist :partition-name)
		    (get init-plist :unit 0)))
  (mount-disk-partition partition :wired? nil :access :write)
  (setq array-for-user (make-array user-elements-per-page
				   :type (case byte-size
					   (8. 'art-8b)
					   (16. 'art-16b))))
  (setq array-as-characters (make-string (* page-size 4)
					 :displaced-to array-for-user)))

(defmethod (partition-output-stream :new-output-buffer) ()
  (values array-for-user 0 user-elements-per-page))

(defmethod (partition-output-stream :send-output-buffer) (buffer ending-index)
  (declare (ignore buffer ending-index))
  (write-disk-block partition next-page-number array-as-characters)
  (incf next-page-number)
  nil)

(defmethod (partition-output-stream :discard-output-buffer) (buffer)
  (declare (ignore buffer))
  nil)

(defmethod (partition-output-stream :set-buffer-pointer) (new-pointer)
  (setq next-page-number (floor new-pointer user-elements-per-page))
  (* next-page-number user-elements-per-page))

(defmethod (partition-output-stream :get-old-data) (buffer-array lower-output-limit)
  (declare (ignore buffer-array lower-output-limit))
  (read-disk-block partition next-page-number array-as-characters)
  nil)

(defmethod (partition-output-stream :max-pointer) ()
  (* (disk-partition-length partition) user-elements-per-page))

(defmethod (partition-output-stream :close) (&optional abortp)
  (declare (ignore abortp))
  (dismount-disk-partition partition))


(defun copy-file-to-partition (filename part &optional unit)
  (with-open-file (file filename)
    (with-open-stream (disk (make-partition-output-stream :partition-name part
							    :unit (default-disk-unit unit)))
      (stream-copy-until-eof file disk)
      (dotimes (j (- (send disk :max-pointer) (send disk :read-pointer)))
	(send disk :tyo #\space)))))

(defun viewf-disk-partition (part &optional unit)
  (with-open-stream (disk (make-partition-input-stream :partition-name part
						       :unit (default-disk-unit unit)))
    (format t "~&~A~%" (symeval-in-instance disk 'partition))
    (stream-copy-until-eof disk standard-output)))


;;;; MCR file reader

(defun read-error-table-data-from-ucode-partition ()
  (multiple-value-bind (part-name unit)
      (loaded-ucode-partition-and-unit)
    (with-open-stream (stream (make-partition-input-stream :partition-name part-name
							   :unit unit
							   :byte-size 8.))
      ;;first the fake section for the entry-point
      (do (section-type
	   adr
	   size
	   (first-time t nil))
	  (())
	(setq section-type (get-part-word stream))
	(setq adr (get-part-word stream))
	(setq size (get-part-word stream))
	(case section-type
	  (1					; I-mem, size in 64 bit words
	   (send stream :set-pointer (+ (send stream :read-pointer)
					(* 8 size))))
	  ((2 4 5)				; Dispatch, A-mem, MID/T-ram; size is 32 bit words
	   (send stream :set-pointer (+ (send stream :read-pointer)
					(* 4 size))))
	  (3					; "Main memory", one argument, except on explorer
						;the first time!
	   (send stream :set-pointer (+ (send stream :read-pointer)
					(* 4 (if first-time 2 1)))))
	  (6
	   (let ((result (condition-case ()
					 (let ((*read-base* 8.)
					       (*package* (find-package 'eh)))
					   `(progn ,(cl:read stream) ,(cl:read stream)))
			   (error nil))))
	     (return result)))
	  (t (return nil)))))))

(defun get-part-word (stream)
  (let* ((b0 (send stream :tyi))
	 (b1 (send stream :tyi))
	 (b2 (send stream :tyi))
	 (b3 (send stream :tyi)))
    (+ (ash b3 24.)
       (ash b2 16.)
       (ash b1 8)
       b0)))


(defun print-ucode-sections (part-name unit)
  (with-open-stream (stream (make-partition-input-stream :partition-name part-name
							 :unit unit
							 :byte-size 8.))
    ;;first the fake section for the entry-point
    (do (section-type
	 adr
	 size
	 (first-time t nil))
	(())
      (setq section-type (get-part-word stream))
      (setq adr (get-part-word stream))
      (setq size (get-part-word stream))
      (case section-type
	(1					; I-mem, size in 64 bit words
	 (format t "~&I-MEM adr ~s size ~s" adr size)
	 (send stream :set-pointer (+ (send stream :read-pointer)
				      (* 8 size))))
	((2 4 5)				; Dispatch, A-mem, MID/T-ram; size is 32 bit words
	 (format t "~&~s adr ~s size ~s"
		 (case section-type
		   (2 "d-mem")
		   (4 "a-mem")
		   (5 "mid or t-ram"))
		 adr size)
	 (send stream :set-pointer (+ (send stream :read-pointer)
				      (* 4 size))))
	(3					; "Main memory", one argument, except on explorer
						;the first time!
	 (format t "~&Main mem: n-blocks ~s adr-of-first-block ~s " adr size)
	 (cond ((null first-time)
		(format t "phys mem adr for first word ~s" (get-part-word stream)))
	       (t
		(format t "explorer version ~s " (get-part-word stream))
		(format t "explorer starting address ~s" (get-part-word stream)))))
	(6
	 (format t "~&Error table adr ~s size ~s" adr size)
	 (return nil))
	(t (return nil))))))

(defun find-instruction-in-i-mem-section (i-mem-adr part unit)
  (declare (values inst partition-offset))
  (setq unit (decode-unit-argument unit "read"))
  (with-open-stream (stream (make-partition-input-stream :partition-name part
							 :unit unit
							 :byte-size 8.))
    ;;first the fake section for the entry-point
    (do (section-type
	 adr
	 size
	 (first-time t nil))
	(())
      (setq section-type (get-part-word stream))
      (setq adr (get-part-word stream))
      (setq size (get-part-word stream))
      (ecase section-type
	(1					; I-mem, size in 64 bit words
	 (when (> i-mem-adr size)
	   (ferror nil "i-mem-adr too big: saved i-mem is ~s insts long" size))
	 (let ((partition-offset (+ (send stream :read-pointer)
				    (* 8 i-mem-adr))))
	   (send stream :set-pointer partition-offset)
	   (let* ((hi (get-part-word stream))
		  (lo (get-part-word stream)))
	     (return (values (+ (ash hi 32.) lo) partition-offset)))))
	((2 4 5)				; Dispatch, A-mem, MID/T-ram; size is 32 bit words
	 (send stream :set-pointer (+ (send stream :read-pointer)
				      (* 4 size))))
	(3					; "Main memory", one argument, except on explorer
						;the first time!
	 (get-part-word stream)
	 (when first-time
	   (get-part-word stream)))
	(6
	 ;;error table
	 (return nil))))))

;;; System Startup File Reader.

(defun execute-system-startup-file ()
  (let ((unit (default-disk-unit nil))
	(part "SSUP"))
    (when (find-disk-partition part nil unit)
      (with-open-stream (disk (make-partition-input-stream :partition-name part
							   :unit unit))
	(let ((s (cond ((eq #/; (send disk :tyipeek))
			(make-echo-stream disk standard-output))
		       ('else
			disk)))
	      (readtable (si:find-readtable-named "CL"))
	      (package (find-package "USER"))
	      (base 10.)
	      (ibase 10.))
	  (catch-error
	    (do ((form)
		 (eof (list nil))
		 (result))
		((or (eq eof (setq form (read s eof)))
		     (eq :EOF form)))
	      (setq result (eval-special-ok form))
	      (unless (eq s disk)
		(terpri standard-output)
		(prin1 result standard-output)))))))))



	      
	      
      
      
  