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

#|

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

|#

(export '( configure
	   deconfigure
	   enable
	   disable
	   setup-network-gauges
	   ))

(defvar *processor-forwarding-alist* nil
  "Alist matching processors with flags indicating whether we are their gateway")

(defvar *protocol-configuration*
	`((:ethernet  (:internet ,ip-ethernet-type (:address :subnet) :address)
		      (:chaos ,chaos-ethernet-type :subnet :address)
		      (:arp ,arp-ethernet-type nil nil))
	  (:loopback  (:internet ,ip-ethernet-type (#x7f000000 #xff000000) #x7f000000)
		      (:chaos ,chaos-ethernet-type nil :address))
	  (:backplane (:internet ,ip-ethernet-type nil :address)
		      (:chaos 0 nil :address))
	  (:serial    (:internet ,ip-ethernet-type nil :address))
	  )
  "List of (hardware-type protocol...) for network interfaces")

(defun configure ()
  ;; If we are in the cold load, do a mini-configuration
  (when si:*in-cold-load-p*
    (cold-load-configure)
    (return-from configure t))

  ;; Clear out current network configuration
  (deconfigure)

  ;; Reset the int-pkts
  (initialize-network-buffers)
  
  ;; Put the network clock function onto the clock list
  (without-interrupts
    (pushnew 'net:network-clock sys:clock-function-list))
  
  ;; Start the device drivers
  (initializations 'network-driver-initialization-list t)
  
  ;; Start the protocol modules
  (when (eq si:*my-op* si:*ethernet-hardware-controller*)
    (arp:setup-arp))
  (chaos:setup-chaos)

  (let ((ip-address-p (ip:setup-ip)))
    (cond (ip-address-p
	   (icmp:setup-icmp)
	   (udp:setup-udp)
	   (tcp:setup-tcp))
	  (t
	   (tv:careful-notify nil t "This processor has no Internet Address")))
  
    (initialize-share-interface)		;Set up address translations and forwarding
    (initialize-loopback-interface)		;Set up address translations

    (when ip-address-p
      ;;Start the TCP/UDP server process and enable network services
      (tcp-application:initialize-tcp-server-process)
      (tcp-application:enable-all-network-services)
      (tcp-application:initialize-udp-rwho-server-process)))

  ;;Start the network receiver process
  (start-receiver)

  (setup-network-gauges)			;If Fancy-Landscape enabled, populate screen with gauges

  t)

(defun initialize-share-interface ()
  (when si:*other-processors*
    ;;KLUDGE: share interface must be set up before chaos, as chaos enables itself on share device --
    ;;but share interface sets %processor-conf-chaos-address, so must reset it after chaos
    ;;has set its address
    (send si:share-interface :reset)
    ;; for all of our other processors, set up ARP translation for share interface
    (setq *processor-forwarding-alist* nil)
    (flet ((forward-p (op)
	     (when (eq ethernet:*3com-owner* si:*my-op*)
	       ;;We forward for other processer IFF we own 3com and they can't take an Excelan
	       (let* ((device (fs:parse-pathname "EXCELAN-NETWORK-INTERFACE:"))
		      (host (and device (send device :host)))
		      (current-owner (and host (send host :owner))))
		 (cond ((eq current-owner :not-on-bus)	;no Excelan
			t)
		       ((> (si:op-proc-number op) 1)
			;;There is an Excelan and the other processor is the 3rd Lisp Machine on a 3X3, or is the
			;;Unix procssor of a 2X2+.  We forward in either case...
			t)
		       ((typep (find-host-for-other-processor op) '(or null fs:unix-host))
			;;There is an Excelan, the other processor is the 2nd on the bus, and is either Unix or
			;;is unknown.  Assume we forward for it.
			t)
		       (t
			;;There is an Excelan and the other processor is the 2nd Lisp Machine.
			;;Wait until it asks us to forward for it...
			nil))))))
      (dolist (op si:*other-processors*)
	(push (cons op (forward-p op)) *processor-forwarding-alist*)
	(dolist (domain '(:chaos :internet))
	  (let ((address (find-network-address-for-other-processor op domain)))
	    (when address
	      (add-address-info address domain si:share-interface op))))))
    ))

(defun initialize-loopback-interface ()
  (dolist (x (ni-address-alist *loopback-interface*))
    ;;For all protocols enabled on the loopback interface, set up address translations
    (when (second x)
      (add-address-info (second x) (first x) *loopback-interface* nil))))

(defun cold-load-configure ()
  ;; Reset the int-pkts
  (initialize-network-buffers)

  ;; Start the device drivers
  (si:set-processor-owning-ethernet :give-up)	;Just find out who owns the various ethernet boards...
  (setup-loopback "LOOPBACK")
  (si:setup-share-interface "SHARE")
  
  ;; Start the protocol modules
  (chaos:setup-chaos)
  
  (initialize-share-interface)			;Set up address translations and forwarding
  (initialize-loopback-interface)		;Set up address translations

  ;;Start the network receiver process
  (start-receiver)
  
  t)

(defun deconfigure ()
  (when *receiver*
    (send *receiver* :flush))
  (when tcp-application:*tcp-server-process*
    (send tcp-application:*tcp-server-process* :flush))
  (when tcp-application:*udp-rwho-server-process*
    (send tcp-application:*udp-rwho-server-process* :flush))
  (when tcp:*tcp-background-process*
    (send tcp:*tcp-background-process* :flush))
  (when ip:*ip-background-process*
    (send ip:*ip-background-process* :flush))
  (tcp-application:disable-all-network-services)
  (dolist (np *network-protocol-streams*)
    (send np :close))
  (dolist (ni *network-interfaces*)
    (send ni :close))
  (without-interrupts
    (setq sys:clock-function-list (delete 'net:network-clock sys:clock-function-list)))
  nil)

(defun disable ()
  (dolist (np *network-protocol-streams*)
    (send np :disable))
  (dolist (ni *network-interfaces*)
    (send ni :disable)))

(defun enable ()
  (dolist (np *network-protocol-streams*)
    (send np :enable))
  (dolist (ni *network-interfaces*)
    (send ni :enable)))

(defun setup-network-gauges ()
  (declare (special tv:fancy-landscape))
  (when (and (boundp 'tv:fancy-landscape) tv:fancy-landscape)
    (dolist (n *network-interfaces*)
      (when (equal (ni-tag n) "ONE")
	(send n :make-gauges)))))

(defun kill-network-gauges ()
  (declare (special tv:fancy-landscape))
  (when (and (boundp 'tv:fancy-landscape) tv:fancy-landscape)
    (dolist (n *network-interfaces*)
      (send n :kill-gauges))
    (dolist (np *network-protocol-streams*)
      (send np :kill-gauges))))

(defvar si:*ethernet-hardware-controller* :unbound
  "The processor description of the processor owning the Ethernet controller.")

(defvar ethernet:*3com-owner* nil "Owner of the 3com board")
(defvar ethernet:*excelan-owner* nil "Owner of the Excelan board")

(defun si:set-processor-owning-ethernet (&optional (operation :find) (board :all))
  "Change the processor controlling the ethernet boards.
If there is no ethernet board, make sure this machine knows not to use it.
If the argument is :FIND (the default) figure out who owns the board so we can
send packets to him.  If no one currently owns it, we allocate it.
If the argument is :TAKE or T, then steal it from the current owner.
If the argument is :GIVE-UP or NIL, then deallocate it so some one else can have it."
  (let ((results nil))
    (when (member board '(:3com :all))
      (push (si:set-3com-owner operation) results))
    (when (member board '(:excelan :all))
      (push (si:set-excelan-owner operation) results))
    ;;If I own either the 3com or excelan, note that I have an ethernet hardware device.
    ;;If I own neither, send through 3com, or excelan if no 3com.
    (let ((owner ethernet:*excelan-owner*))
      (cond ((eq owner si:*my-op*))		;I own excelan?  good...
	    ((null ethernet:*3com-owner*))	;Nobody owns 3com?  Send packets through excelan owner.
	    (t					;Else, send packets through 3com owner.
	     (setq owner ethernet:*3com-owner*)))
      (setq si:*ethernet-hardware-controller* owner))
    results))

(defvar si:dont-use-3com nil
  "leave 3com alone, even if free or already owned by me,
   so that unix / rtime can use it for raw-ethernet")

(defun si:set-3com-owner (operation)
  (let* ((index (* 2 si:%system-configuration-ethernet-owner))
	 (lo (aref si:*sys-conf* index))
	 (hi (aref si:*sys-conf* (1+ index)))
	 (my-slot (si:get-slot-index si:rg-quad-slot))
	 (result nil))
    (flet ((write-ethernet-slot (n)
				(setf (aref si:*sys-conf* index) (ldb (byte 16. 0) n))
				(setf (aref si:*sys-conf* (1+ index)) (ldb (byte 16. 16.) n))))
      (if (and (= lo #o177777) (= hi #o177777))
	  (setq ethernet:*3com-owner* nil result "No 3com hardware.")
	(let ((current-owner (if (ldb-test (byte 1 15.) hi) (si:get-slot-index lo) nil)))
	  (cond ((not (fboundp 'ethernet:setup-3com))
		 (setq ethernet:*3com-owner* nil)
		 (setq result "No 3com driver"))
		(si:dont-use-3com
		 (when (and current-owner (= current-owner my-slot))
		   (write-ethernet-slot 0))	;if I own it, give it up
		 (setq ethernet:*3com-owner* nil)
		 (setq result "3com hardware reserved for raw ethernet"))
		((and (or (null current-owner) (= current-owner my-slot))
		      (or (eq operation :GIVE-UP) (eq operation nil)))
		 (write-ethernet-slot 0)
		 (setq ethernet:*3com-owner* nil)
		 (setq result "No one owns the 3com."))
		((or (null current-owner) (eq operation :TAKE) (eq operation t))
		 (write-ethernet-slot (dpb 1 (byte 1 31.) my-slot))
		 (setq ethernet:*3com-owner* si:*my-op*)
		 (when current-owner
		   (format t
       "~&WARNING: the 3com was owned by the processor in slot ~D but this processor has~
        ~%forcibly taken it.  If the other processor is dead, all is well, but if it is~
        ~%alive, both processors will get incorrect data until it does (net:configure)."
			   current-owner))
		 (setq result "I own the 3com."))
		(t				;:find, or don't own, but :give-up
		 (dolist (op (cons si:*my-op* si:*other-processors*)
			     (si:config-ferror nil "slot ~d (the ethernet owner) not found in sys-conf"
					       current-owner))
		   (when (= current-owner (si:%processor-conf-slot-number (si:op-proc-conf op)))
		     (setq ethernet:*3com-owner* op)
		     (setq result (if (= current-owner my-slot)
				      "I own the 3com."
				    (format nil "Processor in slot ~d owns the 3com." current-owner)))
		     (return)))))))
      (unless (eq ethernet:*3com-owner* si:*my-op*)
	(when (fboundp 'ethernet:disable-lambda-ucode)
	  (ethernet:disable-lambda-ucode)))
      result)))

(defvar si:dont-use-excelan nil
  "leave excelan alone, even if free or already owned by me")

(defun si:set-excelan-owner (operation)
  (let* ((device (fs:parse-pathname "EXCELAN-NETWORK-INTERFACE:"))
	 (host (and device (send device :host)))
	 (current-owner (and host (send host :owner)))
	 (my-slot (si:get-slot-index si:rg-quad-slot))
	 (result nil))
    (cond ((or (null host) (eq current-owner :not-on-bus))
	   (setq ethernet:*excelan-owner* nil)
	   (setq result "No Excelan hardware"))
	  ((not (fboundp 'ethernet:setup-excelan))
	   (setq ethernet:*excelan-owner* nil)
	   (setq result "No excelan driver"))
	  (si:dont-use-excelan
	   (when (and current-owner (= current-owner my-slot))
	     (send host :deallocate))
	   (setq ethernet:*excelan-owner* nil)
	   (setq result "Excelan hardware reserved for raw ethernet"))
	  ((and (or (null current-owner) (= current-owner my-slot))
		(or (eq operation :GIVE-UP) (eq operation nil)))
	   (send host :deallocate)
	   (setq ethernet:*excelan-owner* nil)
	   (setq result "No one owns the Excelan."))
	  ((or (null current-owner) (eq operation :TAKE) (eq operation t))
	   (send host :allocate)
	   (setq ethernet:*excelan-owner* si:*my-op*)
	   (setq result "I own the Excelan."))
	  (t					;:find, or don't own, but :give-up
	   (dolist (op (cons si:*my-op* si:*other-processors*)
		       (si:config-ferror nil "slot ~d (the Excelan owner) not found in sys-conf"
					 current-owner))
	     (when (= current-owner (si:%processor-conf-slot-number (si:op-proc-conf op)))
	       (setq ethernet:*excelan-owner* op)
	       (setq result (if (= current-owner my-slot)
				"I own the Excelan."
			      (format nil "Processor in slot ~d owns the Excelan." current-owner)))
	       (return)))))
    result))

(defun setup-lambda-ethernet (tag)
  (when (eq si:processor-type-code si:lambda-type-code)
    (si:set-processor-owning-ethernet :find :3com)
    (cond ((eq ethernet:*3com-owner* si:*my-op*)
	   (ethernet:setup-3com TAG)
	   (si:set-processor-owning-ethernet :give-up :excelan))
	  (t
	   (si:set-processor-owning-ethernet :find :excelan)
	   (if (eq ethernet:*excelan-owner* si:*my-op*)
	       (ethernet:setup-excelan TAG))))))

(add-initialization "Start Lambda Ethernet interface" '(setup-lambda-ethernet "ONE") :network-driver)

(add-initialization "Configure Network System" '(configure) '(:normal :system))

(add-initialization "Deconfigure Network System"
		    '(progn (deconfigure)
			    (initialize-network-buffers)
			    (cleanup))
		    '(:before-cold))

(defun move-initialization (initialization-list first-name second-name)
  (do* ((first nil)
	(list initialization-list (cdr list))
	(next (second list) (second list)))
       ((null list))
    (cond ((string-equal first-name (car next))
	   (setq first list))
	  ((string-equal second-name (car next))
	   (when first
	     (setf (cdr list) (cddr list))	;Splice out second initialization
	     (setf (cdr first) (nconc (ncons next) (cdr first))))
	   (return))
	  (t))))

(add-initialization "Move network configuration initialization"
		    '(move-initialization si:system-initialization-list "Forget old file access" "Configure Network System")
		    '(:once))

(defun maybe-deconfigure-network ()
  (when (and (boundp '*loopback-interface*)
	     *loopback-interface*
	     (ni-enabled *loopback-interface*))
    (when (global:with-timeout ((* 30 60.) (progn (write-string "Yes (after timeout)" *query-io*) t))
	    (yes-or-no-p "      Deconfigure network (Yes after thirty seconds) ? "))
      (net:deconfigure)))
  (cleanup))

(defun cleanup ()
  (unless (and (boundp '*loopback-interface*)
	       *loopback-interface*
	       (ni-enabled *loopback-interface*))
    ;;Only do this if network is deconfigured
    (chaos:reset nil)
    (setq chaos:made-pkts nil)
    (setq chaos:pkts-made 0)
    (setq chaos:free-pkts nil)
    (setq chaos:los-pkts nil)
    (setq chaos:current-los-pkt-count 0)
    (setq net:*network-interfaces* nil)
    (setq net:*network-protocol-streams* nil)
    (setq ethernet:*3com-ethernet-interface* nil)
    (setq ethernet:*excelan-ethernet-interface* nil)
    (setq ethernet:explorer-ethernet-interface nil)
    (setq chaos:cadr-network-interface nil)
    (setq si:share-interface nil)
    (setq net:*loopback-interface* nil)
    (setq arp:*arp-stream* nil)
    (setq chaos:*chaos-stream* nil)
    (setq ip:*ip-stream* nil)
    (setq ip:*free-ip-headers* nil)
    (setq ip:*route-table* nil)
    (setq ip:*default-network-numbers* nil)
    (setq ip:*default-gateway* nil)
    (setq ip:*default-interface* nil)
    (setq ip:*network-list* nil)
    (setq ip:*queued-packet-fifo* (make-fifo))
    (setq icmp:*icmp-stream* nil)
    (setq icmp:*free-icmp-messages* nil)
    (setq udp:*udp-stream* nil)
    (setq udp:*free-udp-buffers* nil)
    (setq udp:*free-udp-headers* nil)
    (setq tcp:*tcp-stream* nil)
    (setq tcp:*free-tcp-buffers* nil)
    (setq tcp:*free-tcp-headers* nil)
    (setq tcp:*send-blocked-sockets* (make-fifo))
    (setq tcp:*send-blocked-control-packets* (make-fifo))
    (setq tcp:*global-timeouts* nil)
    (setq tcp:*tcp-log* (make-fifo))
    (setq tcpa:*udp-rwho-server-packets* nil)
    (si:clear-resource 'tcp:simple-art-8b-buffer)
    ))

(add-initialization "Maybe Deconfigure Network" '(maybe-deconfigure-network) '(:gc-system-release))
	 
