;;; -*- Mode:LISP; Package:MAP-FAULT; Base:10; Readtable:CL -*-

(defun swap-in-internal (virtual-cluster)
  (let* ((virtual-quantum (vinc:cluster-quantum virtual-cluster))
	 (quantum-map-entry (quantum-map:read-quantum-map virtual-quantum))
	 (status-bits (quantum-map:quantum-status-bits quantum-map-entry))
	 (paging-device (array:svref li:*paging-devices* (quantum-map:quantum-device quantum-map-entry)))
	 (quantum-dqin (quantum-map:quantum-dqin quantum-map-entry))
	 (region (quantum-map:region-origin virtual-quantum))
	 physical-cluster
	 )
    ;; Error checking first.
    ;; To swap in a cluster, its associated quantum must have a valid entry in the quantum map table, i.e.,
    ;; it had to have been written at some point in the past. The entry in the map must be have both the valid
    ;; and mapped bit on at the same time, otherwise it is an error.
    (and (quantum-map:quantum-valid? status-bits)
	 (li:error "quantum ~D has not been allocated yet." virtual-quantum status-bits))
    (and (not (= status-bits quantum-map:$$quantum-mapped))
	 (li:error "Quantum ~D is not mapped to any paging device" virtual-quantum status-bits))
    (when (not paging-device)
      (li:error "Quantum ~D is not associated with any paging device." virtual-quantum))
    ;; get a physical cluster to write to.
    (setq physical-cluster (pcd:allocate-physical-cluster))
    (li:begin-read paging-device quantum-dqin (hw:ldb virtual-cluster (byte 4 0) 0))
    (li:next-read-cluster paging-device physical-cluster)
    (li:do-the-read paging-device)
    ;; map it to virtual-cluster
    (pcd:associate-cluster
      physical-cluster
      virtual-cluster
      pcd:$$status-normal
      (region-bits:region-read-only region)
      pcd:$$cluster-no-read-mar
      pcd:$$cluster-no-write-mar)
    )
  )

(defun page-out-page-and-change-device-for-quantum (virtual-quantum old-dqin region cluster-to-write physical-cluster
						    &optional (from-paging-device-id li:*boot-band-paging-device-id*)
						    (To-paging-device-id li:*page-band-paging-device-id*))
  ;; should check for valid paging device ids.
  (let ((from-paging-device (array:svref li:*paging-devices* from-paging-device-id))
	(To-paging-device (array:svref li:*paging-devices* To-paging-device-id))
	(cluster (vinc:quantum->cluster virtual-quantum))
	new-dqin map-entry)
    (or from-paging-device
	(li:error "No source paging device is specified for quantum ~S" virtual-quantum))
    (or To-paging-device
	(li:error "No destination paging device is specified for quantum ~S" virtual-quantum))
    (setq new-dqin (li:allocate-quantum to-paging-device))
    ;; write out the cluster to be swapped out any way.
    (li:begin-write To-paging-device new-dqin (hw:ldb cluster (byte 4 0) 0))
    (li:next-write-cluster to-paging-device physical-cluster)
    (li:do-the-write to-paging-device)
    ;; now make sure that all the pages still in boot device are copied to paging band device.
    ;; use cluster physical-cluster-to-write for the transfer.
    (dotimes (virtual-cluster-offset 16.)
      (setq cluster (hw:dpb virtual-cluster-offset (byte 4. 10.) cluster))
      (unless (= cluster cluster-to-write)
	(setq map-entry (map:read-map cluster))
	(if (= (map:map-lisp-valid-bit map-entry) 1)
	    ;; already in core. Dirty it and let go.
	    (map:write-map cluster (hw:dpb 1 hw:%%map-lisp-write-enable-bit map-entry))
	  ;; otherwise page in and out to new device.
	  (progn
	    (associate-cluster
	      physical-cluster
	      cluster
	      pcd:$$status-normal
	      (region-bits:region-read-only region) nil nil)
	    (li:begin-read from-paging-device old-dqin (hw:ldb cluster (byte 4 0) 0))
	    (li:next-read-cluster from-paging-device physical-cluster)
	    (li:do-the-read from-paging-device)
	    (li:begin-write To-paging-device new-dqin (hw:ldb cluster (byte 4 0) 0))
	    (li:next-write-cluster to-paging-device physical-cluster)
	    (li:do-the-write to-paging-device))
	  )
	)
      )
    ;; update quantum map.
    (quantum-map:modify-quantum-map virtual-quantum
				    #'(lambda (To)
					(hw:dpb
					  new-dqin
					  quantum-map:%%quantum-map-dqin
					  (hw:dpb
					    to-paging-device-id
					    quantum-map:%%quantum-map-device
					    (hw:dpb 1 quantum-map:%%quantum-map-mapped-bit To)))))
    )
  )

(defun swap-out-internal (physical-cluster)
  (let* ((pcd-data (pcd:read-pcd physical-cluster))
	 (virtual-cluster (pcd:pcd-virtual-cluster-number pcd-data)))
    (when (not (pcd:clean-cluster? pcd-data))
      (let* ((virtual-quantum (quantum-map:cluster-quantum virtual-cluster))
	     (quantum-map-entry (quantum-map:read-quantum-map virtual-quantum))
	     (status-bits (quantum-map:quantum-status-bits quantum-map-entry))
	     (paging-device-id (quantum-map:quantum-device quantum-map-entry))
	     (paging-device (array:svref li:*paging-devices* paging-device-id))
	     (quantum-dqin (quantum-map:quantum-dqin quantum-map-entry))
	     (region (quantum-map:region-origin virtual-quantum))
	     )
	;; Error checking first.
	(dispatch (byte 2 0) status-bits
		  (quantum-map:$$quantum-allocated
		    (setq quantum-dqin (li:allocate-quantum paging-device))
		    (quantum-map:modify-quantum-map
		      virtual-quantum
		      #'(lambda (q) (vinc:dpb-multiple-unboxed
				      quantum-dqin                 quantum-map:%%quantum-map-dqin
				      quantum-map:$$quantum-mapped quantum-map:%%quantum-map-status
				      q))))
		  (quantum-map:$$quantum-mapped nil)
		  (t
		    (li:error "quantum ~D has not been allocated yet." virtual-quantum status-bits)))
	(if (= paging-device-id li:*boot-band-paging-device-id*)
	    (page-out-page-and-change-device-for-quantum
	      virtual-quantum
	      quantum-dqin
	      virtual-cluster
	      physical-cluster
	      li:*boot-band-paging-device-id*
	      li:*page-band-paging-device-id*)
	  ;; just write out the page. it is on the paging band.
	  (progn
	    (li:begin-write paging-device quantum-dqin (hw:ldb virtual-cluster (byte 4 0) 0))
	    (li:next-write-cluster paging-device physical-cluster)
	    (li:do-the-write paging-device)))))
    (map:free-swapped-out-virtual-cluster virtual-cluster)
    (pcd:free-physical-cluster physical-cluster)
    )
  )

(defun swapin-cluster-and-wire-it (virtual-cluster)
  (do ((success nil)
       (address (vinc:cluster->address virtual-cluster)))
      (success)
    (hw:vma-start-read-no-transport-vma-unboxed-md-unboxed address)
    (hw:read-md)
    (hw:nop)
    (hw:nop)
    (trap:without-traps
      #'(lambda ()
	  (let ((map-data (hw:read-map)))
	  (when (= (map:map-lisp-valid-bit map-data) hw:$$map-valid)
	    (let ((pcd (map:map-on-board-address map-data)))
	      (pcd:modify-pcd pcd
			      #'(lambda (pcd-data)
				  (setf (pcd:pcd-status pcd-data) pcd:$$status-wired))))
	    (setq success t))))))
  )

(defun icache-map-fault-handler (pc+)
;  (when (not (= gr::*page-fault-code* *page-fault-code-idle*))
;    (trap::illop "Recursively entered icache page fault handler."))
  (setq gr::*page-fault-code* *page-fault-code-icache-fault*)
  (let* ((address (k2:pc->addr pc+))
	 (map-bits (map:read-map (vinc:cluster-number address))))
    (vinc:flush-icache)
    ;; Don't need to know if they were off because we are called only from
    ;; the trap handler (so far).
    (trap::trap-on)
    (dispatch (byte 4. 0.) (extract-map-status map-bits)
	      ($$map-status-read-only-aged (icache-aged        address map-bits))
	      ($$map-status-swapped-out    (icache-swapped-out address map-bits))
	      (t                           (icache-error       address map-bits pc+)))))

(defun icache-error (address map-bits pc+)
  (trap::illop "Icache-error called."))

(defun icache-aged (vma map-bits)
  (hw:trap-off)
  (hw:write-vma vma)
  (map:touch-aged map-bits)
  (pcd:rejuvenate-cluster (map-on-board-address map-bits))
  (setq gr::*page-fault-code* *page-fault-code-idle*)
  )

(defun icache-swapped-out (vma map-bits)
  (let ((virtual-cluster (vinc:cluster-number vma)))
    (if (fresh-cluster? virtual-cluster)
	(trap::illop "Icache fault on fresh cluster.")
      (swap-in-internal virtual-cluster)))
  (hw:trap-off)
  (setq gr::*page-fault-code* *page-fault-code-idle*))

