;;; -*- Mode:LISP; Package:NETWORK; Readtable:CL; Base:10 -*-

#|

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

|#

(export '( allocate-packet
	   allocate-packet-for-receive
	   free-packet
	   copy-packet
	   max-words-per-pkt
	   max-data-words-per-pkt
	   max-data-bytes-per-pkt))

;;; Here follow routines pertaining to network packets

(defparameter max-words-per-pkt (/ 1518. 2)
  "Largest packet possible in 16-bit words, including header.")
(defparameter max-data-words-per-pkt (/ 1502. 2)
  "Number of 16-bit data words in largest packet.")
(defparameter max-data-bytes-per-pkt 1502.
  "Number of 8-bit data bytes in largest packet.")

(defsubst int-pkt-word-count (int-pkt) (array-leader int-pkt sys:%chaos-leader-word-count))
(defsubst int-pkt-thread (int-pkt) (array-leader int-pkt sys:%chaos-leader-thread))
(defsubst int-pkt-csr-1 (int-pkt) (array-leader int-pkt sys:%chaos-leader-csr-1))
(defsubst int-pkt-csr-2 (int-pkt) (array-leader int-pkt sys:%chaos-leader-csr-2))
(defsubst int-pkt-bit-count (int-pkt) (array-leader int-pkt sys:%chaos-leader-bit-count))

(defvar network-buffer-list nil "the list of all network buffers")
(defvar network-buffer-area)

(defun create-network-buffers (&optional (n-buffers 0))
  (unless (boundp 'network-buffer-area)
    (let* ((leader-length (length sys:chaos-buffer-leader-qs))	;number of elements in array leader
	   (buffer-size (+ 3			                ;leader header, leader length, array header
			   leader-length	                ;the array-leader
			   (ceiling max-words-per-pkt 2)))	;the array-elements
	   (area-size (max #o40000		                ;size of area in q's:  minimum area size
			   (* (ceiling (* n-buffers buffer-size)
				       sys:page-size)
			      sys:page-size))))
      
      (sys:make-area :name 'network-buffer-area
		     :region-size area-size
		     :size area-size
		     :gc :static)

      (setq n-buffers (floor area-size buffer-size))	;make as many buffers as will fit in area

      (dotimes (i n-buffers)
	(let ((int-pkt (zl:make-array max-words-per-pkt
				      :type 'sys:art-16b
				      :area network-buffer-area
				      :leader-length leader-length)))
	  (setf (fill-pointer int-pkt) 0)
	  (push int-pkt network-buffer-list)))))
  
  (si::%wire-area network-buffer-area t)
  
  (do ((tail network-buffer-list (cdr tail)))
      ((null tail))
    (setf (array-leader (car tail) sys:%chaos-leader-thread) (cadr tail)))

  (setf (aref #'sys:system-communication-area sys:%sys-com-chaos-transmit-list) nil)
  (setf (aref #'sys:system-communication-area sys:%sys-com-chaos-receive-list) nil)
  (setf (aref #'sys:system-communication-area sys:%sys-com-chaos-free-list) (car network-buffer-list))
  )

;;; Interrupt (microcode) related specials
(defvar int-free-list-pointer)			;Freelist used by microcode
(defvar int-receive-list-pointer)		;Packets received at interrrupt level
(defvar int-transmit-list-pointer)		;Packets to be transmitted at interrupt level

(defsubst int-free-list () (sys:%p-contents-offset int-free-list-pointer 0))
(defsubst int-receive-list () (sys:%p-contents-offset int-receive-list-pointer 0))
(defsubst int-transmit-list () (sys:%p-contents-offset int-transmit-list-pointer 0))

(defun initialize-network-buffers (&optional (n-buffers 0))
  (without-interrupts
    (setq int-free-list-pointer (locf (aref #'sys:system-communication-area sys:%sys-com-chaos-free-list))
	  int-receive-list-pointer (locf (aref #'sys:system-communication-area sys:%sys-com-chaos-receive-list))
	  int-transmit-list-pointer (locf (aref #'sys:system-communication-area sys:%sys-com-chaos-transmit-list)))
    (create-network-buffers n-buffers)
    ))

(defsubst check-packet (int-pkt)
  (= (sys:%area-number int-pkt) network-buffer-area))

(defun assure-safe-int-pkt (int-pkt)
  (cond ((not (and (arrayp int-pkt)
		   (eq (global:array-type int-pkt) 'global:art-16b)
		   (net:check-packet int-pkt)))
	 (error "~s is not a valid int-pkt" int-pkt))))

(defun allocate-packet (&optional (wait-if-necessary t) &aux int-pkt free-list)
  "Allocates a new INT-PKT.  May have to wait for one, so be careful
that it is ok that the process this gets called from can be safely suspended"
  (loop
    (setq free-list (int-free-list))
    (cond ((null free-list)
	   (if wait-if-necessary
	       (process-wait "Network buffer" #'(lambda () (int-free-list)))
	     (return nil)))
	  ((sys:%store-conditional-double int-free-list-pointer
					  free-list
					  (locf (int-pkt-thread free-list))
					  (int-pkt-thread free-list))
	   (return (setq int-pkt free-list)))))
  (and int-pkt (setf (int-pkt-thread int-pkt) nil))
  int-pkt)

(defun allocate-packet-for-receive (interface)
  "Allocates an int-pkt non-blockingly.  If none are available, notes that one is needed
in the interface structure and returns NIL"
  (cond ((ni-reserved-receive-buffer interface)	;if buffer reserved, return it and forget it
	 (prog1 (ni-reserved-receive-buffer interface)
		(setf (ni-reserved-receive-buffer interface) nil)))
	((allocate-packet nil))			;else, if can allocate a buffer, do so
	(t					;else, count failure and note that we need a buffer
	 (incf (ni-allocation-failures interface))
	 (setf (ni-need-receive-buffer interface) t)
	 nil)))

(defun free-packet (int-pkt)
  "Returns an INT-PKT to the free list"
  ;;Allow a displaced array to be freed (presumeably skipping link-level headers, etc.)
  (setq int-pkt (original-array int-pkt))
  (unless (check-packet int-pkt)
    (error "Attempt to free non-interrupt packet ~A" int-pkt))
  (unless (null (int-pkt-thread int-pkt))
    (error "Attempt to free an already-freed int-pkt"))
  (prog (old-free-list)
     loop
	(setq old-free-list (int-free-list))
	(setf (int-pkt-thread int-pkt) old-free-list)
	(or (sys:%store-conditional int-free-list-pointer old-free-list int-pkt)
	    (go loop))
	(sys:%chaos-wakeup)))

(defun count-packets ()
  (int-pkt-list-length (int-free-list)))

(defun copy-packet (packet interface &optional no-hang-p)
  (declare (ignore interface))
  (let ((int-pkt (allocate-packet no-hang-p))
	(nw (fill-pointer packet)))
    (when int-pkt
      (copy-array-portion packet 0 nw int-pkt 0 nw)
      (setf (fill-pointer int-pkt) nw))
    int-pkt))

(DEFUN PRINT-INT-PKT (INT-PKT)
  (DO ((I 0 (1+ I)))
      ((>= I  (INT-PKT-WORD-COUNT INT-PKT)))
    (FORMAT T "~%Word ~O, data ~O" I (AREF INT-PKT I))))

(DEFUN PRINT-INT-PKT-STATUS (&OPTIONAL PRINT-ALL)
  (FORMAT T "~%Free list ~d, transmit-list ~d, receive-list ~d"
	  (INT-PKT-LIST-LENGTH (sys:SYSTEM-COMMUNICATION-AREA sys:%SYS-COM-CHAOS-FREE-LIST))
	  (INT-PKT-LIST-LENGTH (sys:SYSTEM-COMMUNICATION-AREA sys:%SYS-COM-CHAOS-TRANSMIT-LIST))
	  (INT-PKT-LIST-LENGTH (sys:SYSTEM-COMMUNICATION-AREA sys:%SYS-COM-CHAOS-RECEIVE-LIST)))
  
  (WHEN PRINT-ALL
    (let* ((area network-buffer-area)
	   (region (si:%area-region-list area))
	   (origin (si:%region-origin region))
	   (free-pointer (sys:%pointer-plus (si:%region-free-pointer region) origin)))
      (do ((l (sys:%make-pointer sys:dtp-locative origin)
	      (sys:%make-pointer-offset sys:dtp-locative l (sys:%structure-total-size l)))
	   int-pkt)
	  ((>= (sys:%pointer-difference l free-pointer) 0))
	(when (or (not (= (sys:%p-data-type l) sys:dtp-header))
		  (not (= (sys:%p-ldb sys:%%header-type-field l) sys:%header-type-array-leader)))
	  (error "bad structure in ~s" (sys:area-name area)))
	(setq int-pkt (sys:%make-pointer-offset sys:dtp-array-pointer
					    l
					    (sys:%p-ldb sys:%%array-leader-length l)))
	(let* ((x (int-pkt-bit-count int-pkt))
	       (ethernet-type-number (if x (swap-two-bytes x))))
	  (format t
		  "~%Ethernet type ~a on ~a"
		  (case ethernet-type-number
		   (#.chaos-ethernet-type 'chaos)
		   (#.arp-ethernet-type 'addr-res)
		   (#.xns-ethernet-type 'xns)
		   (#.ip-ethernet-type 'ip)
		   (otherwise ethernet-type-number))
		  (int-pkt-on-any-list int-pkt)
		  ))))))

(DEFUN INT-PKT-LIST-LENGTH (HEAD)
  (WITHOUT-INTERRUPTS
    (DO ((C 0 (1+ C))
	 (PKT HEAD (INT-PKT-THREAD PKT)))
	((or (NULL PKT)
	     (> c 1000.))
	 C))))

(DEFUN INT-PKT-LIST-MEMQ (ITEM INT-PKT-LIST)
  (WITHOUT-INTERRUPTS
    (DO ((PKT INT-PKT-LIST (INT-PKT-THREAD PKT))
	 (c 0 (1+ c)))
	((or (NULL PKT)
	     (> c 1000.))
	 nil)
      (IF (EQ PKT ITEM) (RETURN T)))))

(defun int-pkt-on-any-list (item)
  (without-interrupts
    (cond ((int-pkt-list-memq item (int-free-list)) 'free-list)
	  ((int-pkt-list-memq item (int-receive-list)) 'receive-list)
	  ((int-pkt-list-memq item (int-transmit-list)) 'transmit-list))))

(add-initialization "Initialize Network Buffers" '(initialize-network-buffers) '(:once))
