This Multics source file was rescued from the messed-up source archive at MIT.

Process Exchange Switch Stack, the Multics scheduler and dispatcher.

For a description of the scheduling algorithm, see Bob Mullen's "The Multics Scheduler."

Back to Multics Source index.

            pxss.alm                        08/11/83  1813.9r   08/11/83  1735.1     1224261



"  ******************************************************
"  *                                                    *
"  *                                                    *
"  * Copyright (c) 1972 by Massachusetts Institute of   *
"  * Technology and Honeywell Information Systems, Inc. *
"  *                                                    *
"  *                                                    *
"  ******************************************************

" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   pxss -- The Multics Traffic Controller (Scheduler)
"
"   Last Modified: (Date and Reason)
"
"   April 1983 by E. N. Kittlitz to DRL instead of 0,ic looping.
"   February 1983 by E. N. Kittlitz for hex floating point.
"   December 1982 by C. Hornig to punt NCP
"   October 1982 by C. Hornig for no ITT message on fast channels.
"   August 1982 by J. Bongiovanni for realtime_io priority, 
"       relinquish_priority
"   April 1982 by J. Bongiovanni to enhance governing
"   February 1982 by J. Bongiovanni to fix masking bug
"   September 1981 by J. Bongiovanni for procs_required, moving
"       code to tc_util
"   June 1981 by J. Bongiovanni for governed work classes,
"       -tcpu, +pre_empt_sample_time
"   May 1981 by J. Bongiovanni for response time metering
"   03/01/81 by W. Olin Sibert, for Phase One of ADP conversion
"   March 1981 by J. Bongiovanni for page pinning, saved stack_0's,
"             argument copying protocol, initialization NTO
"   February 1981 by J. Bongiovanni for fast connect
"   March 1981 by E. Donner for new ipc - include file for itt entry
"   and to change check for fast channel
"   February 1981 by J. Bongiovanni to fix set_proc_required
"   January 1981 by J. Bongiovanni to fix ITT overflow, credit
"             clipping
"   Spring 1979 by B. Greenberg for shared stack_0's.
"   Fall 1978 RE Mullen for +ptl_NTO, +disable int_q, -XED's
"   Winter 1977 RE Mullen for lockless (lockfull?) scheduler:
"       concurrent read_lock, ptlocking state, apte.lock,
"       unique_wakeup entry, tcpu_scheduling
"   Spring 1976 by RE Mullen for deadline scheduler
"   02/17/76 by S. Webber for new reconfiguration
"   3/10/76 by B Greenberg for page table locking event
"   Spring 1975 RE Mullen to implement priority scheduler and
"       delete loop_wait code.  Also fixed plm/lost_notify bug.
"   Last modified on 02/11/75 at 19:49:10 by R F Mabee.  Fixed arg-copying & other bugs.
"   12/10/74 by RE Mullen to add tforce, ocore, steh, tfmax & atws
"        disciplines to insure response in spite of long quanta, and
"        fix bugs in get_processor, set_newt, and loop_wait unthreading.
"   12/6/74 by D. H. Hunt to add access isolation mechanism checks
"   4/8/74 by S.H.Webber to merge privileged and unprivileged code.
"       and to add quit priority and fix lost notify bug
"   5/1/74 by B. Greenberg to add cache code
"   8/8/72 by R.B. Snyder for follow-on
"   2/2/72 by R. J. Feiertag to a simulated alarm clock
"   9/16/71 by Richard H. Gumpertz to add entry rws_notify
"   7/**/69 by Steve H. Webber
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

    name    pxss


    iftarget    adp
      warn  (This has not been converted yet for the ADP. Beware.)
    ifend


    link    prds_link,prds$+0

    even
channel_mask_set:
    oct 17,17

null:   its -1,1        null pointer

null_pk:
    oct 007777000001    null packed pointer

null_epaq:
    vfd 3/0,15/-1,18/0,18/1,18/0
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   Table of contents
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "


    entry   addevent
    entry   block
    entry   delevent
    entry   dvctl_retry_ptlwait
    entry   empty_t
    entry   fast_ipc_block
    entry   fast_ipc_get_event 
    entry   force_stop
    entry   free_itt
    entry   get_entry
    entry   get_event
    entry   guaranteed_eligibility_off
    entry   guaranteed_eligibility_on
    entry   i_stop
    entry   io_wakeup
    entry   ips_wakeup
    entry   ips_wakeup_int
    entry   lock_apt
    entry   lock_apte
    entry   notify
    entry   page_notify
    entry   page_wait
    entry   pre_empt
    entry   ptl_notify
    entry   ptl_wait
    entry   relinquish_priority
    entry   ring_0_wakeup
    entry   set_cpu_timer
    entry   set_procs_required
    entry   set_timer
    entry   set_work_class
    entry   start
    entry   stop
    entry   stop_wakeup
    entry   thread_in_idle
    entry   unique_ring_0_wakeup
    entry   unlock_apt
    entry   unlock_apte
    entry   unthread_apte
    entry   usage_values
    entry   wait
    entry   waitp
    entry   wakeup
    entry   wakeup_int

"
    include apte
"
    include aste
"
    include drl_macros
"
    include ips_mask_data
"
    include itt_entry
"
    include mc
"
    include mode_reg
"
    include ptw
"
    include pxss_page_stack
"
    include response_transitions
"
    include scs
"
    include sst
"
    include stack_0_data
"
    include stack_frame
"
    include stack_header
"
    include state_equs
"
    include tc_meters
"
    include wcte
"

    macro   read_clock
    rccl    sys_info$clock_,*
    &end

"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   BLOCK -- entry to block a process.
"
"   Call is
"       call pxss$block;
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

block:  eppbp   short_ret
    sprpbp  pds$ipc_block_return

    equ ipcv.retsw,1

fast_ipc_block:
    tsplb   setup_mask  mask, switch_stack
    aos bb|te_block
    aos bb|blocks
    lda bp|apte.flags   check for wakeup waiting
    cana    apte.wakeup_waiting,du
    tnz block_ez        able to avoid heavy lock!
    tsx7    update_te       update te in own APT entry
    tsx6    WRITE_LOCK  block locks
    tsx6    LOCK_bp     block locks
    lda bp|apte.flags   check for wakeup waiting
    cana    apte.wakeup_waiting,du
    tnz block_not       there is one, return
    ldq BLOCK_PROCESS,dl
    tsx7    meter_response_time$tc  this is a response transition
    tsx7    unthread        thread out of ready list
    ldx0    blocked,du  set state to blocked
    tsx7    update_execution_state
    tsx7    revoke_elig block
    tsx7    reschedule  block
    tsx7    purge_UNLOCK    block
    tsx7    getwork
    ldaq    bp|apte.virtual_cpu_time note vtime at gain elig
    staq    pds$virtual_time_at_eligibility
    tsx6    LOCK_bp
block_returns:
    lcx0    apte.wakeup_waiting+1,du turn off wakeup waiting
    ansx0   bp|apte.flags
return_event_messages:
    lda bp|apte.flags   check for interrupts pending
    ana apte.stop_pending,du look for stop connect
    ora bp|apte.ips_message or ips signals
    tze *+2     if interrupt pending leave 1
    lda 1,dl        ..
    ldq bp|apte.flags2  get special interrupts
    anq apte.special_chans,dl ..
    ersq    bp|apte.flags2  turn off special chans
    qls apte.chans_offset   ..
    lls 17      put info together
    eax3    0,al        remember info
    eax4    0       use to zero event thread
    ldx2    bp|apte.ipc_pointers return event thread
    stx4    bp|apte.ipc_pointers zero event thread in APT entry
    tsx6    UNLOCK_bp
    tsx7    switch_back_ret_pds
    stz pds$itt_head
    stx2    pds$itt_head
    eaq 0,3     get info
    lda 0,dl        zero a
    lls 1       split info
    orsq    pds$events_pending  store special chans
    orsa    pds$ipc_vars+ipcv.retsw store return sw
    lprpbp  pds$ipc_block_return
    epbpsb  pds$stack_0_ptr,*   Load this up for fast_hc_ipc,
                "who can't do it himself.
    tra bp|0

"
"here if find wakeup_waiting after full lock, to back off big lock
"
block_not:
    tsx6    UNLOCK      only need self locked
    tra block_returns

"
"here if notice wakeup_waiting before locking anything
"
block_ez:   tsx6    LOCK_bp     only need to lock self
    tra block_returns

" 
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " 
"
"   GET_EVENT -- procedure to return a process' event thread
"
"   Call is
"       call pxss$get_event(event)
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

get_event:
    eppbp   short_ret
    sprpbp  pds$ipc_block_return
fast_ipc_get_event:
    tsplb   setup_mask
    tsx6    LOCK_bp
    tra return_event_messages
" 
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   WAKEUP
"
"   call pxss$wakeup(processid,event_channel,event_message,state)
"
"   entry to wake up a process given event channel and event message
"
"   The following entries have the same calling sequence:
"
"      wakeup - send wakeup, give interactive credit if blocked
"         more than priority_sched_inc
"
"      ring_0_wakeup - send wakeup, give interactive credit if blocked
"
"      unique_ring_0_wakeup - send wakeup only if this wakeup is unique
"                in ITT for this process, give interactive credit if
"          blocked.
"
"      io_wakeup - send wakeup, give interactive credit if blocked,
"                give realtime credit if blocked and tuning parameter
"          set.
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

ring_0_wakeup:
wakeup_int:
    tsx6    setup_
    lca 1,dl        set flag saying ring_0_wakeup entry
    sta pds$wakeup_flag set flag saying ring_0_wakeup entry
    tra wjoin
unique_ring_0_wakeup:
    tsx6    setup_
    stz pds$wakeup_flag set flag for unique_ring_0_wakeup
    tra wjoin
io_wakeup:
    tsx6    setup_
    lca 2,dl        set flag for io_wakeup entry
    sta pds$wakeup_flag set flag for io_wakeup entry
    tra wjoin
    
wakeup:
    tsx6    setup_
    stc1    pds$wakeup_flag set flag saying wakeup entry
wjoin:
    lda ap|2,*      get processid
    sta pds$arg_1
    ldaq    ap|4,*      save event channel in pds$arg_2
    staq    pds$arg_2
    ldaq    ap|6,*      save event message in pds$arg_3
    staq    pds$arg_3
    tsplb   setup_check switch stacks and lock
    arg 0,6

    szn pds$wakeup_flag see if ring_0_wakeup entry
    tmoz    w_rz        it is. do later code
    lda pds$validation_level it isn't. 
    sta tmp_ring        get ready to make ITT message
    stz dev_signal
    tra copy_evs        skip code for ring_0_wakeup entry
w_rz:   stz tmp_ring
    lda 1,dl
    sta dev_signal  must be a device signal
copy_evs:
    ldaq    pds$arg_2       finish setup for make ITT message
    staq    tmp_ev_channel
    ldaq    pds$arg_3
    staq    tmp_ev_message

    ldx2    0,du        pre-set execution state to zero
    tsx6    WRITE_LOCK  Wakeup may need to thread in
    tsx7    hash_LOCK       wakeup finds and locks
    arg wakeup_returns_nul  indirect if error
    lda bp|apte.flags   see if this is idle process
    cana    apte.idle,du
    tnz wakeup_returns  can't wakeup an idle process
    aos bb|wakeups
    lxl0    bp|apte.state   check for stopped process
    cmpx0   stopped,du
    tze wakeup_returns

    ldq NON_TTY_WAKEUP,dl
    tsx7    meter_response_time$tc  response transition

    ldq pds$wakeup_flag should we give priority ?
    tpnz    non_ring_0  no,normal wakeup
    lxl0    bp|apte.state   recover process state
    cmpx0   blocked,du  no interaction credit unless blocked
    tnz no_int_credit   ..at time of wakeup.
    lda apte.interaction,du give priority ... turn on interaction sw.
    orsa    bp|apte.flags   in APT entry of process getting wakeup
    cmpq    =-2     io_wakeup?
    tnz no_int_credit   no
    szn bb|realtime_io_priority_switch are we giving realtime priority?
    tze no_int_credit   no
    lda apte.realtime_burst,du
    orsa    bp|apte.flags   yes
no_int_credit:
    tsx7    make_itt_message    wakeup adds itt_msg to process' queue
    tsx7    wake        Go to common wakeup code
    lxl2    bp|apte.state   return arg in x2
    szn errcode
    tze *+2
    lxl2    errcode
wakeup_returns:
    tsx6    UNLOCK_bp       Wakeup unlocks target apte
wakeup_returns_nul:
    tsx6    UNLOCK      Wakeup unlocks
    tsx7    switch_back_ret Exit from wakeup if pid invalid and no apte locked
    stz ap|8,*      return execution state
    sxl2    ap|8,*
    short_return

non_ring_0:
"Here are the checks for the
"Access Isolation Mechanism

    ldaq    bp|apte.access_authorization if target has ipc privilege
    orq pds$access_authorization+1 or if sender has ipc privilege,
    canq    apte.no_ipc_check,dl
    tnz no_int_credit   then it is OK to send the wakeup.   
    ldx0    bp|apte.access_authorization+1 get level of target process
    cmpx0   pds$access_authorization+1 if it's less than sender's level,
    tmi send_down       do not allow wakeup to be sent.
    ana pds$access_authorization if the category set of the sender
    cmpa    pds$access_authorization is contained in (or equal to)
    tze no_int_credit   the category set of the target,
"then it is OK to send the wakeup.
send_down:
    ldx2    100,du      this error code indicates
    tra wakeup_returns  an IPC send-down attempt.

" 
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   WAKE -- internal subroutine used to wake up a process
"   and award it a processor if it is warrented.
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

wake:
    tsx6    subroutine_save
    szn bp|apte.lock    ASSUME bp locked
    drlnz   (pxss: APTE not locked) ASSUME bp locked
    lda apte.wakeup_waiting,du turn on wakeup waiting flag
    orsa    bp|apte.flags
    lxl0    bp|apte.state   make sure process is blocked
    cmpx0   blocked,du
    tnz subroutine_unsave
    read_clock      " check for long time block
    sbaq    bp|apte.state_change_time subtract out last state chage time
    cmpq    bb|priority_sched_inc see if process has been asleep too long
    tmi short_time  just went blocked, no priority
    tsx0    setup_p_int boost priority
short_time:
    ldx0    ready,du        change state to ready
    tsx7    update_execution_state
    tsx7    sort_in     schedule and thread in
    eax2    bp|0        fp = entry waking up
    tsx7    get_processor   does he get a processor?
    tra subroutine_unsave
" 
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   FREE_ITT - entry to put a list of ITT entries into the list of free ITT 
"       entries. It is always called after a process returns from block, 
"       to release its event message queue.
"
"       Nothing is locked when this code is entered.
"       Apte's are locked to validate that the processes exist.
"       If the pid has changed we know empty_t zeroed the counter.
"       Never looplock an APTE while holding the itt_free_list.
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

free_itt:
    tsx6    setup_
    lda ap|2,*      pick up pointer to list to be freed
    sta pds$arg_1
    tsplb   setup_check switch stacks and lock
    arg 0,6

    ldx0    pds$arg_1       get head of process's itt queue
    tsx7    free_itt_       free them
    tra switch_back free_itt exits
"

free_itt_:
"Come here to free X0->ITTE's
    eax5    0,0     Remember newly freed head in X5
    tze free_itt_exit   there is nothing to free
    epbpbb  tc_data$        get pointer to base of tc_data
    ldx4    -1,du       put all-seven into RX4
    eaa 0       count num freed
follow_itt_list:
    sxl4    bb|itt_entry.next_itt_relp,0  tag the discarded entry for debugging
    sba 1,dl        maintain counter
    ldx3    bb|itt_entry.origin,0 see if dev_signal
    tnz fi_skip_sender  nz means dev_signal
    ldx3    bb|itt_entry.sender,0 was dev_signal
    tsx6    LOCK_x3     free_itt locks sender
    ldq bb|apte.processid,3 if process still exists
    cmpq    bb|itt_entry.sender,0
    tnz fi_sender_gone  processid has changed!
    lcq 1,dl        ok to decrement
    asq bb|apte.ittes_sent,3
fi_sender_gone:
    tsx6    UNLOCK_x3       free_itt unlocks sender
fi_skip_sender:
"               get following entry's forward pointer
    ldx2    bb|itt_entry.next_itt_relp,0
    tze thread_in_itt_queue
    eax0    0,2     put index 2 into XR0
    tra follow_itt_list
"
"x5->first_freed, x0 ->last_freed, A has count
thread_in_itt_queue:
    ldqc    bb|itt_free_list    free_itt LOCKS free list
    tnz *+2
    tra thread_in_itt_queue If zero somebody else has it

    eax1    0,qu
"               make tail of new -> old head
    stx1    bb|itt_entry.next_itt_relp,0
    ldx3    bb|itt_entry.target_id,0 prepare to decrement target counter
    ldq bb|itt_entry.target_id,0 get rcvr pid
    asa bb|used_itt use A-reg to decrement
    stx5    bb|itt_free_list    free_itt UNLOCKS free_list
    tsx6    LOCK_x3     free_itt locks target
    cmpq    bb|apte.processid,3 compare processid
    tnz rcvr_gone       processid has changed!
    asa bb|apte.ittes_got,3 use A-reg to decrement
rcvr_gone:
    tsx6    UNLOCK_x3       free_itt unlocks target
free_itt_exit:
    tra 0,7     free_itt_  exits

" 
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   PRE_EMPT
"   TIMER_RUNOUT
"
"   This procedure is called at timer runout time and
"   when a process gets pre-empted by a higher priority
"   process. Pre-empt merely gives the processor away.
"   Timer-runout gives up eligibility as well.
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

pre_empt:
    tsx6    init_pxss_save_stack init x7 save stack
    tsx7    update_te
    aos bb|te_pre_empt
    lda bp|apte.flags
    cana    apte.idle,du    Idle suffers pre-empt not timer-runout
    tnz pmt_idle
    lda bp|apte.temax   See if time left in quantum
    cmpa    bp|apte.te
    tmi tro_        None left give up elig

pmt_:   tsx6    LOCK_bp     pre-empt changes state
    lcx0    apte.pre_empt_pending+1,du turn OFF flag
    ansx0   bp|apte.flags   ..
    ldx0    ready,du        set state to ready
    tsx7    update_execution_state
    tsx6    UNLOCK_bp       pre-empt unlocks before getwk
    tsx7    getwork
    tra wired_fim$pre_empt_return pmt returns

pmt_idle:   szn bb|wait_enable  If idle make sure not shutting down
    tnz pmt_        Idle pmt ok if multiprog
    szn bb|system_shutdown
    tze pmt_        Idle pmt ok if not shutdown
    tra wired_fim$pre_empt_return Must not do getwork!

tro_:   tsx6    WRITE_LOCK  tro_ unthreads
    tsx6    LOCK_bp     tro_ locks apte
    lcx0    apte.pre_empt_pending+1,du
    ansx0   bp|apte.flags
    tsx7    unthread        tro_
    ldx0    ready,du        tro_
    tsx7    update_execution_state tro_
    tsx7    revoke_elig tro_
    tsx7    reschedule  tro_
    tsx7    sort_in     tro_
    tsx7    purge_UNLOCK    tro_
    tsx7    getwork     tro_
    ldaq    bp|apte.virtual_cpu_time note vtime at gain elig
    staq    pds$virtual_time_at_eligibility
    tra wired_fim$pre_empt_return tro returns

" 
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   stop     - called by hcs_$stop_proc to stop a process
"       stop is unusual in that it is a call side
"       operation which must unthread and sort_in
"       a ready non-eligible process
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

stop_wakeup:
stop:
    tsx6    setup_
    lda ap|2,*      get processid
    sta pds$arg_1
    tsplb   setup_check switch stacks and lock
    arg 0,6
    tsx6    WRITE_LOCK  stop may need rethread

    lda pds$validation_level get caller's ring number
    sta tmp_ring

    ldx2    0,du        pre-set execution state to zero
    tsx7    hash_LOCK       stop finds and locks target
    arg stop_returns_nul    indirect if error

    lda bb|default_procs_required   set  process to default
    ana apte.procs_required_mask,du just in case
    drlze   (pxss: APTE disdains all processors) should never happen
    era bp|apte.procs_required
    ersa    bp|apte.procs_required
    ldx2    apte.default_procs_required,du set default flag
    orsx2   bp|apte.flags

    lxl2    bp|apte.state   target's state to x2
    cmpx2   stopped,du  Compare to the stopped state
    tze stop_returns    If equal, target already stopped
    stz dev_signal  count it as dev_signal
    aos dev_signal  count it as dev_signal
    lda =aquit      put recognizable pattern in message
    ldq =astop      ... namely "quitstop"
    staq    tmp_ev_channel
    staq    tmp_ev_message  pattern into channel & message
    stc1    pds$wakeup_flag not require unique message
    tsx7    make_itt_message    now go make "quitstop" message

    tsx0    setup_p_int boost priority
    lxl0    bp|apte.state
    cmpx0   blocked,du  Blocked?
    tze st_wake     Yes, wake him.

    ldx0    bp|apte.flags   Eligible?
    canx0   apte.eligible,du
    tnz st_wake     Yes, don't unthread
    tsx7    unthread        No, move up in queue
    tsx7    sort_in_before

st_wake:    tsx7    wake        Awaken blocked target
    lda apte.stop_pending,du turn on stop pending
    orsa    bp|apte.flags
    tsx7    send_connect    send connect if processor running
    lxl2    bp|apte.state   return state to caller
stop_returns:
    tsx6    UNLOCK_bp
stop_returns_nul:
    tsx6    UNLOCK      stop is done
    tsx7    switch_back_ret
    stz ap|4,*      return execution state
    sxl2    ap|4,*
    short_return
" 
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   start    - called by hcs_$start_proc to start a process
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

start:
    tsx6    setup_
    lda ap|2,*      get processid
    sta pds$arg_1
    tsplb   setup_check switch stacks and lock
    arg 0,6

    tsx6    WRITE_LOCK  start must protect pid, rethreads
    ldx2    0,du        pre-set state to zero
    tsx7    hash_LOCK       Get pointer to apt entry
    arg stop_returns_nul    start: indirect if error
    lcx0    apte.stop_pending+1,du turn off stop pending
    ansx0   bp|apte.flags
"   lxl0    bp|state        pick up target's state
"   cmpx0   stopped,du  Compare it to stopped state
"   tnz stop_returns    If not equal, return
    ldx0    blocked,du  Otherwise redefine target state
    tsx7    update_execution_state
    tsx7    wake        And awaken target
    lxl2    bp|apte.state   return target's state
    tra stop_returns    return
" 
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   I_STOP -- stop connect handler. This connect is used
"   to stop a running process. The process puts itself in the stopped
"   state, gives away eligibility, and finally the processor.
"
" " " " " " " " " " " " " " " " " " " " " " "" " " " " " " " " " " "" "

i_stop:
"
"   We must first check to see if the connect went off in ring 0
"
    eppbp   ap|2,*      get pointer to machine conditions
    eppbp   bp|0,*      ..
    lda bp|mc.scu.ppr.prr_word get word containing PRR
    cana    scu.ppr.prr_mask,du see if ring is non-zero
    tze delay_it        it's zero. delay the connect
"
"   Continue normally
"
force_stop:
    tsplb   setup_mask
    aos bb|te_i_stop
    tsx7    update_te
    tsx6    WRITE_LOCK  I_stop unthreads
    tsx6    LOCK_bp
    lda bp|apte.flags
    cana    apte.stop_pending,du check for stop pending
    tze i_stop_not  no, return
    szn bp|apte.term_processid  Is there a buzzard for this process?
    drlze   (pxss: No term_processid) NO - CRASH
    ldaq    bp|apte.alarm_time  check for alarm pending
    tze i_stop_getwork  no alarm pending

    eax0    bp|0        thread out of alarm list
    cmpx0   bb|alarm_timer_list see if first on list
    tnz is_scan

    ldx2    bp|apte.alarm_time  if so thread out
    stx2    bb|alarm_timer_list
    tra i_stop_getwork

is_scan:    ldx2    bb|alarm_timer_list search list for entry
is_loop:    cmpx0   bb|apte.alarm_time,2
    tze is_done
    ldx2    bb|apte.alarm_time,2
    tra is_loop
is_done:    ldx0    bp|apte.alarm_time  thread out of list
    stx0    bb|apte.alarm_time,2

i_stop_getwork:
    tsx7    unthread        istop
    ldx0    stopped,du  set state to stopped
    tsx7    update_execution_state
    tsx7    revoke_elig istop
    tsx7    reschedule  istop
    tsx7    purge_UNLOCK    istop
    eax4    0
    ldx0    bp|apte.ipc_pointers
    stx4    bp|apte.ipc_pointers
    tsx7    free_itt_
    tsx7    getwork
    drltra  (pxss: Returned from getwork) should never get here, nohow

delay_it:
    eppbp   pds$apt_ptr,*   check for stop_pending flag on
    lda bp|apte.flags   ..
    cana    apte.stop_pending,du ..
    tze stop_return ignore interrupt
    lda 1,dl        set ring alarm to 1
    sta pds$alarm_ring
    lra pds$alarm_ring
stop_return:
    short_return

i_stop_not:
UNLOCK2_switch_back:
    tsx6    UNLOCK_bp       i_stop done
    tsx6    UNLOCK      i_stop done
    tra switch_back
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   set_procs_required - entry to set and reset the
"       group of CPUs on which this process can run.
"   
"   call pxss$set_procs_required (cpu_set, code)
"       cpu_set = bit (8) aligned CPU mask
"           ("0"b => set to system 




default)
"       code    = non-standard error code
"           0 => group set and now running in group
"           ^0 => no member of group online
"
"
"   system default is a CPU mask stored in tc_data$default_procs_required
"   It is used for processes which have not requested explicitly
"   CPUs required, and for those which have reset to the default.
"   To avoid various races (with reconfiguration, default changing, etc.),
"   this cell should not be used or set without the global APT lock held
"
"   THIS ENTRY MUST NOT BE CALLED ON THE PRDS
"
"   It may, however, be called from a wired environment
"   (the reason for non-standard error codes)
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

set_procs_required:
    ldx0    lp|prds_link    check whether we're on the prds
    cmpx0   sb|stack_header.stack_begin_ptr      already
    drlze   (pxss: sprq already on prds) invalid call--can't do much else
    
    tsx6    setup_
    ldq ap|2,*      save argument in pds
    stq pds$arg_1
    stz ap|4,*      clear error code
    tsplb   setup_check switch stacks and lock
    arg 0,6
    tsx6    WRITE_LOCK  sprq locks out getwork and reset_proc_req

    eax2    0       assume set_procs (to non-default)
    ldq pds$arg_1       refetch the argument
    anq apte.procs_required_mask,du strip out garbage
    tnz pr_set      set
    ldq bb|default_procs_required   system default
    anq apte.procs_required_mask,du shouldn't be garbage, but ...
    drlze   (pxss: APTE disdains all processors) we do this, nobody's running anyway
    eax2    1       reset (to default)
pr_set:
    canq    scs$processor   is any CPU in the set online
    tze UNLOCK_sprq_error   yes--don't set, return error code
    eaa 0,qu        save group mask for check   
    tsx6    LOCK_me_bp  sprq
    erq bp|apte.procs_required  set into APTE
    anq apte.procs_required_mask,du
    ersq    bp|apte.procs_required

    eax2    0,2     set to default
    tnz set_default_flag    yes
    lcx0    apte.default_procs_required+1,du
    ansx0   bp|apte.flags
    tra set_reset_done
set_default_flag:
    ldx0    apte.default_procs_required,du
    orsx0   bp|apte.flags
set_reset_done:

"
"   Now check to see if we're on a CPU in the group. If not
"   go thru getwork (which won't run us unless we're on such a CPU).
"
    cana    prds$processor_pattern
    tnz UNLOCK2_switch_back already on one, unlock everything and return

    call    page$cam        clear all caches if wrong cpu
    tsx7    update_te       get set for getwork
    ldx0    ready,du
    tsx7    update_execution_state
    tsx6    UNLOCK_bp       sprq unlock for getwk
    tsx6    UNLOCK      sprq unlocks before getwork
    tsx7    getwork
    eax7    0       short_return after switch back
    tra switch_back_ret_pds

UNLOCK_sprq_error:
    tsx6    UNLOCK
    tsx7    switch_back_ret_pds
    stc1    ap|4,*      error code
    short_return

" 
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   EMPTY_T  --  procedure to thread an APT entry into the APT
"   free list.
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

empty_t:
    tsx6    setup_
    ldaq    ap|2,*      save input apt pointer
    staq    pds$arg_1
    tsplb   setup_check switch stacks and lock
    arg 0,6
    tsx6    WRITE_LOCK  emtpty_t rethreads
    eppbp   pds$arg_1,* get pointer to desired APT entry
    tsx6    LOCK_bp     empty_t changes state
    lxl0    bp|apte.state   Check state first
    cmpx0   empty,du        Already empty OK
    tze et_1
    cmpx0   stopped,du  Also stopped OK
    drlnz   (pxss: empty_t APTE not stopped or empty)
    ldx0    empty,du
    tsx7    update_execution_state Changed stopped to empty.
et_1:
    eax7    0
    ldx0    bp|apte.ipc_pointers claim ITTE's while locked, free later
    stx7    bp|apte.ipc_pointers
    stz bp|apte.ittes_sent      safe to zero these since piud=0
    stz bp|apte.ittes_got

"   Return any stack_0 owned by the defunct process

    lda bp|apte.flags
    cana    apte.shared_stack_0,du  Is there as stack_0 to return?
    tze check_stack_0_none      No

    tsx6    lock_stack_queue
    eaa bp|0        au = apte offset
    arl 18      al = apte offset
    ldq -1,du       comparison mask
    lxl0    ab|sdt.num_stacks   number of stack_0's
    eax4    0       index into sdt
check_stack_0_loop:
    cmk ab|sdt.aptep,4  this stack_0 belong to deadproc
    tnz bump_next_stack_0   no
    tsx6    free_stack_0    yes--give it up
    ldx0    1,du
    asx0    bb|max_max_eligible number available stack_0's
    lca 1,dl
    asa bb|stopped_stack_0  count of suspended stack_0's
    tra check_stack_0_done
bump_next_stack_0:
    eax4    sdte_size,4 bump sdt index
    eax0    -1,0        one less sdte
    tpnz    check_stack_0_loop  transfer if more to go
check_stack_0_done:
    tsx6    unlock_stack_queue
    
check_stack_0_none:
    tsx6    UNLOCK_bp
    ldx4    bb|empty_q  thread into free list
    stx4    bp|apte.thread  singly threaded
    eax4    bp|0        get pointer to this entry
    stx4    bb|empty_q  to update into empty_q
    tsx6    UNLOCK      empty_t uses  lock to protect empty_q
    eax4    0
    ldx0    bp|apte.ipc_pointers
    stx4    bp|apte.ipc_pointers
    tsx7    free_itt_
    tra switch_back
" 
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   UPDATE_EXECUTION_STATE -- subroutine to store the execution
"   state passed in x0 into the APTE pointed to by bp. The
"   appropriate counters in tc_data are also updated.
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

update_execution_state:
    szn bp|apte.lock    ASSUME bp locked
    drlnz   (pxss: APTE not locked) ASSUME bp locked
    lda bp|apte.flags   dont change meters for idle
    cana    apte.idle,du
    tnz update_exec_ret
    lxl4    bp|apte.state   get previous (old) state
    lca 1,dl
    asa bb|statistics,4
    aos bb|statistics,0
"old_assume_state_change_ok
update_exec_ret:
    sxl0    bp|apte.state
    read_clock      " read the clock
    staq    bp|apte.state_change_time for state change time
    tra 0,7



" 
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   UNTHREAD -- procedure to thread the APT entry pointed to by 
"   bp out of the list it is in.
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

unthread:
    szn tc_data$apt_lock    ASSUME write locked
    drlmi   (pxss: APT not locked) ASSUME write locked
    szn bp|apte.thread  check if not in a list
    tze 0,7     return if not in a list
    lxl4    bp|apte.thread  x4 -> previous entry in list
    drlze   (pxss: unthread null back ptr) ASSUME cur.bp nonzero
    eax0    bp|0        ASSUME prev.fp -> cur
    cmpx0   bb|apte.thread,4    ASSUME prev.fp -> cur
    drlnz   (pxss: unthread prev.fp ^= cur) ASSUME prev.fp -> cur
    ldx0    bp|apte.thread  x0 -> next entry in list
    drlze   (pxss: unthread null cur.fp) ASSUME cur.fp nonzero
    stx0    bb|apte.thread,4    store old forward in previous
    sxl4    bb|apte.thread,0    store old back in next
    stz bp|apte.thread  zero thread pointers
    tra 0,7
" 

" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   GET_ENTRY  -  returns pointer to empty entry
"
"   On return, apte is unthreaded, unlocked, and procs_required
"   is set to the system default. Other fields are cleared.
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

get_entry:
    tsplb   setup_mask
    tsx6    WRITE_LOCK  get_entry unthreads
    ldx0    bb|empty_q  x0 points to first entry on list
    tnz available_entries   ok if non-zero
    eppbp   null,*      None available
    tra get_entry_returns   So return
available_entries:
    lca 1,dl        Decrement count of free APTE's
    asa bb|statistics+empty Caller must AOS new state
    ldx1    bb|apte.thread,0    thread entry out of free list
    stx1    bb|empty_q
    eppbp   bb|0,0
    tsx6    LOCK_bp     get_entry locks before zeroing
    mlr (),(pr),fill(0) Zero out APTE
    desc9a  0,0
    desc9a  bp|0,size_of_apt_entry*4
    ldx0    apte.default_procs_required,du  system default
    orsx0   bp|apte.flags
    lda bb|default_procs_required
    ana apte.procs_required_mask,du
    drlze   (pxss: APTE disdains all processors) never happen
    sta bp|apte.procs_required
    tsx6    UNLOCK_bp       get_entry makes apte lockable
get_entry_returns:
    tsx6    UNLOCK      get_entry done
    tsx7    switch_back_ret
    spribp  ap|2,*      return pointer to new APT entry
    short_return


"

" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   SET_WORK_CLASS -- entry to move a process from one work_class to
"   another.  Call is:
"       call pxss$set_work_class (processid, new_wc, old_wc, code)
"
"   processid   -bit (36) aligned specifies process (INPUT)
"   new_wc  -fixed bin specifies new work class (INPUT)
"   old_wc  -fixed bin previous value of work_class (OUTPUT)
"   code    -fixed bin. 0=>OK, 1=>bad_processid, 2=>bad_work_class (OUTPUT)
"
"   The steps are:
"   1. Find apte given processid.
"   2. Compute old_wc from apte.wct_index.
"   3. Compute new wct_index from new_wc.
"   4. If ready & not eligible move to new queue.
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

set_work_class:
    tsx6    setup_
    lda ap|2,*      get processid
    sta pds$arg_1
    lda ap|4,*      get new_wc
    sta pds$arg_2       into wired storage.

    tsplb   setup_check switch stacks and lock
    arg 0,6
    tsx6    WRITE_LOCK  set_work_class rethreads, protects pid
    stz pds$arg_3       Clear old_wc now.
    lda 1,dl        Assume processid bad,
    sta pds$arg_4       set code to 1.
    tsx7    hash_LOCK
    arg swc_ret_nul indirect if error
    lda bp|apte.flags   If process is idle
    cana    apte.idle,du    skip craziness.
    tnz swc_ret     Return code = 1.

    aos pds$arg_4       Preset code to 2, bad new_wc
    ldq bp|apte.wct_index   Pick up old index into WCT.
    eax1    0,qu        oldwc in X1
    sbq bb|min_wct_index    Convert it to a number.
    div size_of_wct_entry,du The first wc_num is zero.
    stq pds$arg_3       Reurn old_wc.

    ldq pds$arg_2       Pick up new wc number.
    tmi swc_ret     Return code = 2, bad new_wc.
    mpy size_of_wct_entry,du Convert number to index.
    adq bb|min_wct_index
    cmpq    bb|max_wct_index    Make sure not oob on WCT
    tpnz    swc_ret     Return code = 2, bad new_wc.
    lda bb|wcte.flags,qu    Make sure this wc is defined.
    cana    wcte.defined,du
    tze swc_ret     Return code = 2, bad new_wc.
    stbq    bp|apte.wct_index,60 OK- set new value.
    stz pds$arg_4       Clear code now.

    szn bp|apte.thread  Threaded in some queue?
    tze swc_ret     No. All done.
    lda bp|apte.flags   Yes.
    cana    apte.eligible,du    In the eligible queue?
    tnz swc_elig        Yes. Don't mess around.
    tsx7    unthread        Not eligible, remove from curr rdyq.
    lda bp|apte.ts  If ts is non-zero then sort in before
    sta before      but if ts is zero, sort in after.
    lda bp|apte.ti  Prepare to jump into sort subr
    tsx7    sort_in_again   which requires ti in the A.
swc_ret:
    tsx6    UNLOCK_bp       set_wc unlocks APTE
swc_ret_nul:
    tsx6    UNLOCK      set_wc unlocks
    lxl2    pds$arg_4       Get pseudo errcode
    tsx7    switch_back_ret
    lda pds$arg_3       Return output args to caller.
    sta ap|6,*
    xec swc_code_table,2    Load A with real errcode
    sta ap|8,*

    short_return

swc_elig:
    lca 1,dl
    asa bb|wcte.nel,1   Reduce former wc
    aos bb|wcte.nel,qu  Increm new
    tra swc_ret
"
"Table to map err = 0|1|2 to real (but unwired) error_code
swc_code_table:
    eaa 0       No error
    lda error_table_$bad_processid
    lda error_table_$bad_work_class



"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   GUARANTEED_ELIGIBILITY_ON - this primitive guarantees that this
"   process has at least a certain minimum amount of time left in its
"   eligibility quantum. The primitive does not return until the process will
"   retain eligibility for the specified minimum time. Also if the process
"   loses its eligibility it is given a higher priority scheduling quaranteeing
"   the process some fixed percentage of an eligibility slot. The high priority
"   scheduling is in effect until turned off.
"
"   GUARANTEED_ELIGIBILITY_OFF - this primitive turns off the high
"   priority scheduling granted by guarranteed_eligibility_on.
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

guaranteed_eligibility_on:
    eppbp   pds$apt_ptr,*   set bp-> own apte
    lda apte.prior_sched,dl Turn on high priority mode
    orsa    bp|apte.flags2
    lda 4,du        Boost time slice
    asa bp|apte.temax
    short_return

guaranteed_eligibility_off:
    eppbp   pds$apt_ptr,*   set bp-> own apte
    lca apte.prior_sched+1,dl turn off priority scheduling
    ansa    bp|apte.flags2  ..
    lda bp|apte.temax   Reduce time slice if need be
    tmi ge_off_ret
    sba 4,du
    tpl *+2     Leave positive, tho ..
    lda 1000,dl
    sta bp|apte.temax
ge_off_ret:
    short_return
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   RELINQUISH_PRIORITY
"
"   This primitive lowers the traffic control priority of the
"   invoking process by moving it to the tail of the eligible queue.
"   This is intended for long-running ring-0 functions which operate
"   as background (e.g., the scavenger). A long-running ring-0
"   function migrates to the head of the eligible queue and thereby
"   gains a high priority.
"   
"   call pxss$relinquish_priority
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

relinquish_priority:
    tsplb   setup_mask      " Switch stacks and mask
    tsx6    WRITE_LOCK      " We rethread
    tsx6    LOCK_bp         " And change state

    tsx7    unthread            " Remove from eligible queue
    ldx0    ready,du
    tsx7    update_execution_state  " Change state from running
    eax1    bb|eligible_q_tail      " Thread to end of eligible queue
    tsx7    thread_him_in
    
    tsx6    UNLOCK_bp
    tsx6    UNLOCK
    aos bb|relinquishes     " Meter
    tsx7    getwork
    tra switch_back_pds

"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   SET_TIMER  -- entry to set the per-process cpu timer.
"   Call is:
"       call pxss$set_timer(delta_t, ev_channel)
"
"   dcl delta_t fixed bin (35), /* time to be added to current time.
"       ev_channel fixed bin (71)
"
"   If ev_channel is zero an IPS signal will be sent rather than
"   a wakeup.
"   Nothing need be locked for setting timer, provided the
"   time_out cell is cleared while operating.  This is so getwork
"   will ignore the timer channel if we happen through getwork.
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

set_timer:
    eppbp   pds$apt_ptr,*   Get ptr to own apte
    fld 0,dl        Put zero in AQ
    staq    pds$timer_time_out  Cancel any timer
    ldaq    ap|4,*      Now safe to set channel
    staq    pds$timer_channel

    fld 0,dl        Want two words but given one
    ldq ap|2,*      Pick up delta_t
    tmoz    zero_chan       if delta_t <= 0 then reset
    tra rel_time        else set relative timer

set_cpu_timer:
    eppbp   pds$apt_ptr,*   Get ptr to own apte
    fld 0,dl        Cancel any timer
    staq    pds$timer_time_out  Now if we getwork we will not have timer go off
    ldaq    ap|6,*      safe to set channel now
    staq    pds$timer_channel

    ldaq    ap|2,*      Put time arg in AQ

    lxl0    ap|4,*      pick up timesw arg
    cmpx0   2,du        Absolute timer?
    tze abs_time        If so go do it
    cmpx0   1,du        Relative timer?
    tze rel_time        If so then relative

zero_chan:
    fld 0,dl        resetting--timeout already zero
    staq    pds$timer_channel
    short_return

rel_time:   adaq    bp|apte.virtual_cpu_time
abs_time:   staq    pds$timer_time_out  Now safe to set timeout
    short_return
" 
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   PXSS$LOCK_APT       PXSS$UNLOCK_APT
"
"   Externally available entries to manipulate
"   apt lock.  Caller must be wired and masked.
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

lock_apt:   push
    tsx6    WRITE_LOCK  Give caller protection.
    return


unlock_apt:
    push
    tsx6    UNLOCK
    return

"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " 
"
"   PXSS$LOCK_APTE
"
"   Externally available routine to lock an APTE
"
"   call pxss$lock_apte (processid, aptep, code)
"
"   processid  is Input
"   aptep is set on return (null if APTE) not locked
"   code = 0 => APTE locked
"        ^=0 => processid invalid
"
"   Caller must be wired and masked and real careful
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " 

lock_apte:
    push
    eppbb   tc_data$
    lda ap|2,*      processid
    sta pds$arg_1       for hash_LOCK
    stc1    ap|6,*      non-zero return code
    tsx7    hash_LOCK       Try to lock APTE
    arg lock_apte_null  Fail
    stz ap|6,*      Succeed - zero return code
lock_apte_null:
    spribp  ap|4,*      aptep
    return

" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " 
"
"   PXSS$UNLOCK_APTE
"
"   Externally available entry to unlock an APTE
"
"   call pxss$unlock_apte (aptep)
"
"   aptep is a pointer to the APTE to unlock
"   caller should still be masked and wired
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " 

unlock_apte:
    push
    eppbp   ap|2,*      ptr to aptep
    eppbp   bp|0,*      aptep
    epbpbb  bp|0        tc_data
    tsx6    UNLOCK_bp       Unlock APTE
    return
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " 
"
"   Subroutines to lock and unlock the APT
"   Meaning of lock values is
"   +N  APT is locked for single writer with caller||cpu_tag
"   0   lock is busy/hidden/out_to_lunch
"   -1  APT is unlocked
"   -(N+1)  APT is locked for N readers
"
"   Note that the lock value is claimed by a
"   primitive LDAC and restored by a primitive STA.
"   Note that lock_loops take special care not to
"   keep lock in busy state (~anti-hog hardware)
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " 

READ_LOCK:
    read_clock      " setup for metering looplocks
    staq    temp
    eax5    0       but assume no need to meter looplocks
read_:
    ldac    tc_data$apt_lock
    tpnz    r_put       was write locked
    tze read_       was busy try again
    sba 1,dl        was unlocked or read locked
    sta tc_data$apt_lock    one more reader
rw_ret: eax5    0,5     See if looplock metering needed
    tze 0,6
    read_clock  
    sbaq    temp
    asq tc_data$loop_lock_time
    aos tc_data$loop_locks
    tra 0,6

r_put:  sta tc_data$apt_lock    restor locks state
r_wait: szn tc_data$apt_lock    loop till unlocked or readlocked
    tmi read_       was unlocked or read locked
    eax5    1       force looplock metering
    tra r_wait      was busy or write locked


WRITE_LOCK:
    read_clock  
    staq    temp
    eax5    0
write_:
    ldac    tc_data$apt_lock
    cmpa    unlocked_APT
    tnz w_fail      busy or lockd for read or write
    eaa 0,6     lock with caller||cpunum
    ada prds$processor_tag
    sta tc_data$apt_lock
    tra rw_ret      join looplock meter code
w_fail: cmpa    0,dl        if was not busy
    tze w_wait
    sta tc_data$apt_lock    then restor lock value
w_wait: lda unlocked_APT    loop till unlocked
    cmpa    tc_data$apt_lock
    tze write_
    eax5    1       force looplock metering/
    tra w_wait


UNLOCK: ldac    tc_data$apt_lock
    tmi unread
    tze UNLOCK      busy so retry
    lda unlocked_APT    was write locked so now unlock
    tra unlock_
unread: ada 1,dl        one less reader
    drlze   (pxss: unlock apt read lock bad count) DEBUG somebody lostcount
unlock_:    sta tc_data$apt_lock
    tra 0,6


WRITE_TO_READ:
    ldac    tc_data$apt_lock
    tpnz    w_to_r      got lock
    tze WRITE_TO_READ
    drltra  (pxss: write_to_read bad lock count) somebody lost count
w_to_r:
    cmpa    tc_data$apt_lock    DEBUG
    drlze   (pxss: write_to_read ldac failed) DEBUG ldac must have failed to clear
    lca 2,dl        set lock value to one reader
    sta tc_data$apt_lock
    tra 0,6

unlocked_APT:
    dec -1

"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " 
"
"   APTE LOCKING PROCEDURES
"   apte is locked if apte.lock = 0
"   apte is unlocked if apte.lock ^= 0
"   Note that address of caller of unlock is saved in apte.lock
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " 

LOCK_me_bp:
    eppbp   pds$apt_ptr,*   get ptr to own apte
    epbpbb  bp|0        get ptr to base of tcdata
LOCK_bp:
    sznc    bp|apte.lock
    tnz 0,6
    tra LOCK_bp

UNLOCK_bp:
    szn bp|apte.lock    DEBUG
    drlnz   (pxss: UNLOCK_bp not locked) DEBUG
    stx6    bp|apte.lock    Remember last unlocker
    tra 0,6

LOCK_x2:
    sznc    bb|apte.lock,2
    tnz 0,6
    tra LOCK_x2

UNLOCK_x2:
    szn bb|apte.lock,2  DEBUG
    drlnz   (pxss: UNLOCK_X2 not locked) DEBUG
    stx6    bb|apte.lock,2  Remember last unlocker
    tra 0,6

LOCK_x3:
    sznc    bb|apte.lock,3
    tnz 0,6
    tra LOCK_x3

UNLOCK_x3:
    szn bb|apte.lock,3  DEBUG
    drlnz   (pxss: UNLOCK_x3 not locked) DEBUG
    stx6    bb|apte.lock,3  Remember last unlocker
    tra 0,6

"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   stack switching subroutines
"
"   A routine which holds the global traffic control lock or an
"   APTE lock cannot be interrupted in a way which would cause
"   reinvocation of pxss.  For this reason, pxss must run on the
"   PRDS.  However, most of its entries may be called from an
"   unwired environment.  To accomplish this, arguments to pxss
"   are copied into wired storage in the PDS prior to any stack
"   switching.  This copying may result in loss of control of
"   a CPU and reinvocation of pxss (e.g., because of a page
"   fault), and overwriting of the PDS cells used for argument
"   storage.  To avoid this interference, all entries of pxss
"   which can be called from an unwired environment adhere to
"   following protocol:
"
"   tsx6    setup_
"
"             (Copy arguments to PDS and set any flags used as temporaries
"      in the PDS - x6 and pr0 must be preserved in this code)
"
"   tsplb   setup_check
"   arg 0,6
"     (Next Instruction)
"
"   On return to (Next Instruction), we are running on the PRDS
"   with interrupts masked, and the values of all temporaries in
"   the PDS are guaranteed to be those set between the call to
"   setup_ and the call to setup_check.  These values are
"   guaranteed to be safe until either getwork is called or one of
"   the routines to switch stacks back is called.
"
"   Routines which can be called only from a wired environment
"   must set the cell pds$pxss_args_invalid to non-zero if they
"   use ANY temporaries in the PDS.
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   setup_
"
"   subroutine to mark begin of argument copy to PDS
"
"   tsx6    setup_
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

setup_: stz pds$pxss_args_invalid   clear interference flag
    tra 0,6         return to next instruction

" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   setup_check
"
"   subroutine to check for successful copy of temporaries
"
"   tsplb   setup_check
"   arg <return if args must be re-copied>
"   <return if args copied successfully, wired and masked>
"
"   On successful return, bp -> apte for this process
"                         bb -> base of tc_data
"                   ap has been saved in pds$tc_argp
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

    inhibit on      <+><+><+><+><+><+><+><+><+><+><+><+><+><+><+><+><+>   
setup_check:
    sznc    pds$pxss_args_invalid Did we get overwritten?
    tnz lb|0,*      Cause err exit, get args copied again.
    epplb   lb|1        Fix return address.

"   Fall through to setup_mask

" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   setup_mask
"
"   mask to sys_level, save current mask in pds$tc_mask (unless on PRDS) and
"   switch stacks to PRDS (by invoking setup)
"
"   tsplb   setup_mask
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

setup_mask: 
    spriap  pds$tc_argp
    eppab   sp|0
    ldx0    lp|prds_link    are we on the PRDS
    cmpx0   sb|stack_header.stack_begin_ptr
    tze easy        yes - already masked - fall thru
    lxl1    prds$processor_tag
    lprpab  scs$mask_ptr,1  get set for masking
    xec scs$read_mask,1
    staq    pds$tc_mask THIS MUST BE WIRED!
    ldaq    scs$sys_level
    xec scs$set_mask,1

"   Fall through to setup 
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   setup
"
"   switch stacks to PRDS.  If stacks are indeed
"   switched (not running on PRDS already), the current stack
"   pointer is saved in pds$last_sp
"
"   setup is intended to be called directly only by page control
"   "side doors", where it is known that we are running on
"   the PRDS.  These "side doors" must not call any switch_back_xxx
"   routines, as this would re-load an interrupt mask saved elsewhere.
"
"   tsplb   setup
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
    
setup:
    eppab   sp|0        save stack pointer
    ldx0    lp|prds_link    are we on prds ?
    cmpx0   sb|stack_header.stack_begin_ptr
    tze easy        yes, easy save

    sprisp  pds$last_sp save pointer to previous stack frame
    eppsp   prds$+stack_header.stack_begin_ptr,* get ptr to new frame
    epbpsb  sp|0        get stack base ptr
easy:   push
    spriab  sp|stack_frame.prev_sp save prev sp
    eppbp   pds$apt_ptr,*   get own apt pointer
    epbpbb  bp|0        set bb to point at base of tc_data
    tsx6    init_pxss_save_stack
    tra lb|0        return

"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   routines to switch back to calling environment
"
"   These routines all restore the interrupt mask to the value
"   saved in pds$tc_mask and the value of ap to the value
"   saved in pds$tc_argp
"
"   tsx7    switch_back_xxx
"   (Next Instruction)
"
"   switch_back_pds - return to caller of pxss on unwired stack
"
"   switch_back - pop a stack frame, switching stacks if necessary,
"       return to caller of pxss
"
"   switch_back_ret - pop a stack frame, switching stacks if necessary,
"       return to (Next Instruction)
"
"   switch_back_ret_pds - switch to unwired stack, return to
"       (Next Instruction)
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
    
switch_back_pds:            "return uncond. to pds history
    eax7    0
    tra restore_sp

switch_back:
    eax7    0       flag to distinguish from switch_back_ret
switch_back_ret:
    ldx0    lp|prds_link    do we have to switch stacks
    cmpx0   sp|stack_frame.prev_sp ..
    tnz restore_sp  yes, skip simple switch
    eppsp   sp|stack_frame.prev_sp,* go back one frame
    tra no_change_mask  and keep masked
restore_sp:
switch_back_ret_pds:
    eppsp   pds$last_sp,*   switch to other stack
    ldaq    prds$+stack_header.stack_begin_ptr restore stack end ptr
    staq    prds$+stack_header.stack_end_ptr ..

    epbpsb  sp|0
    ldaq    pds$tc_mask restore previous mask
    oraq    channel_mask_set
    anaq    scs$open_level
    lxl1    prds$processor_tag
    lprpab  scs$mask_ptr,1  get set for masking
    xec scs$set_mask,1
no_change_mask:
    stc1    pds$pxss_args_invalid Signal possible mutual interference.
    eax7    0,7     check who made call
    tze short_ret
    eppap   pds$tc_argp,*   Get pxss arg list
    tra 0,7

    inhibit off     <-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><->

init_pxss_save_stack:
    eaa pxss_save_stack get address of save stack base
    ora pxss_stack_size*64,dl set up a tally word for storing into pxss_save_stack
    sta pxss_stackp stash this away
    tra 0,6     return to caller


subroutine_save:
    stx7    pxss_stackp,id  store x7 in save stack using tally word
    ttf 0,6     return to the caller if tally not runout
    drltra  (pxss: subroutine_save stack overflow) die - we have run off the save stack

subroutine_unsave:
    ldx7    pxss_stackp,di  pop value of x7 (also updating tally word properly)
    tra 0,7

send_connect:
    ldq bp|apte.flags   see if processor to be stopped is running
    canq    apte.dbr_loaded,du  by checking the dbr-loaded flag
    tze 0,7     not running, return

    eax0    bp|0        APTE offset in X0
    cmpx0   pds$apt_ptr+1   is this process target of connect?
    tze delay_connect   if so, just set ring alarm for now

    ldq bp|apte.flags2  is running- must send cpu stop connect
    anq apte.pr_tag_mask,dl leave only processor tag
    cioc    scs$cow_ptrs,ql*    zap the processor
    tra 0,7     return to caller

delay_connect:
    lda 1,dl        set ring alarm register
    sta pds$alarm_ring  ..
    lra pds$alarm_ring  ..
    tra 0,7     and return to caller

" 
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   MAKE_ITT_MESSAGE
"   
"   subroutine to make a wakeup-associated message,
"   allocate an entry for it in the ITT, and add this entry
"   to the tail of this process' event queue in the APT.
"
"   Caller must set pds$wakeup_flag to control whether non-unique
"   messages will be sent.
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

make_itt_message:
" bp points to process' APT entry and must not be changed
    szn bp|apte.lock    ASSUME bp locked
    drlnz   (pxss: APTE not locked) ASSUME bp locked
    stz errcode     we have run into no problem yet

    lda tmp_ev_channel  check for special channel
    cmpa    null_pk     if null packed ptr
    tnz mimj2       if not, continue

    lxl0    tmp_ev_channel+1    get special channel index
    lda =o400000,du get wakeup bit
    arl apte.chans_offset-1,0 move to correct cell
    ana apte.special_chans,dl change only special channels
    orsa    bp|apte.flags2  put in APT
    tra 0,x7        " return

mimj2:
    ldac    bb|itt_free_list    Grab head
    tmi itt_overflows   there are no more entries available
    tze *-2

    eax1    0,au        move head to X1
"               XR0 contains forward thread
    ldx0    bb|itt_entry.next_itt_relp,1
    stx0    bb|itt_free_list    restor free-list-head

    aos bb|used_itt count ITT entry usage
    lda tmp_ring        get caller's validation ring
    sta bb|itt_entry.origin,1 put ring number in ITT message
    lda pds$processid   get this process (sender's) ID
    sta bb|itt_entry.sender,1 put sender ID in ITT message
    eax3    0,au        X3 -> sender
    lxl0    dev_signal  get origin flag(1=dev-signal)
    tnz *+2     if not dev_signal
    aos bb|apte.ittes_sent,3 charge sender
    stx0    bb|itt_entry.origin,1 store in origin LHE
    aos bp|apte.ittes_got   charge rcvr
    lda pds$arg_1       get processid of target
    sta bb|itt_entry.target_id,1 store target process' ID in ITT message
    ldaq    tmp_ev_message  get event message
    staq    bb|itt_entry.message,1 put event message in ITT entry
    ldaq    tmp_ev_channel  get target event channel's name
    staq    bb|itt_entry.channel_id,1 put channel name in ITT message, and AQ-reg
"               zero thread this entry will be the last
    stz bb|itt_entry.next_itt_relp,1

"
"Check situation and counts to see if we allow usage of itte.
    stz count       if count is zero there is no per channel limit
    eax5    0
    lda bb|used_itt if U < C/4 then no checks
    cmpa    bb|cid4
    tmi mim_check_done
    lda bb|initializer_id   load A-reg for mi_iz_xxx
    ldx0    bb|itt_entry.origin,1 dev_signals are different
    tnz mim_check_ds    nz means dev_signal
"Checks for non-dev_signal
    ldq bb|cid2
    tsx6    mm_iz_either    D = C/2 or 0
    adq bb|cid3
    cmpq    bb|apte.ittes_sent,3
    tmi unmake_message  if sent > D + C/3 then REJECT
    cmpq    bp|apte.ittes_got
    tmi unmake_message  if got > D + C/3 then REJECT

    sbq bb|cid3
    adq bb|cid2
    sbq bb|apte.ittes_sent,3
    sbq bp|apte.ittes_got
    tmi unmake_message  if sent+got > D + C/2 then REJECT

    ldq bb|itt_size
    sbq bb|used_itt
    sbq bb|apt_size
    tmi unmake_message  if U > C then REJECT
    tra mim_check_done

"Checks for dev_signals
mim_check_ds:
    ldq bb|cid4
    cmpq    bp|apte.ittes_got   if got <  C/4 then OK
    tpl mim_check_done
    qls 18      else chan_limit =  C/4
    stq count
mim_check_done:
" append this message to the tail of the process' event queue
    ldx0    bp|apte.ipc_pointers get head of process' event message queue
    tnz find_queue_end  go follow queue to end
    eax0    0,1     load XR0 from XRR1
    stx0    bp|apte.ipc_pointers this entry is first in q
    tra 0,7     end of event message production
find_queue_end:
    ldaq    tmp_ev_channel  event channel for compare
    szn pds$wakeup_flag should we insure uniqueness?
    tze mumloop     yes - go to that loop
    szn count       must we count wkups on chan?
    tnz mumloop     yes - go to that loop

mimloop:
"               get forward thread from next entry
    ldx4    bb|itt_entry.next_itt_relp,0
    tze append_itt_message  it is the end of the queue
    eax0    0,4     XR0 points to next entry
    tra mimloop

mumloop:    cmpaq   bb|itt_entry.channel_id,0 this chan = new?
    tze mum_ck
mum_ok: ldx4    bb|itt_entry.next_itt_relp,0
    tze maybe_append_itt_message x0 -> last itte
    eax0    0,4
    tra mumloop

mum_ck:
    eax5    1,5     Another itte for this channel
    szn pds$wakeup_flag
    tnz mum_ok      not unique entry, just counting
    ldaq    tmp_ev_message  is msg = new?
    cmpaq   bb|itt_entry.message,0
    tnz mum_ck_ret  not same - go fix AQ then loop more
    ldaq    bb|itt_entry.origin,1 test origin,ring,target
    cmpaq   bb|itt_entry.origin,0
    tze unmake_message  everthing same=> dont need itte
mum_ck_ret:
    ldaq    tmp_ev_channel  restor chan to AQ for compare
    tra mum_ok


maybe_append_itt_message:
    szn count
    tze append_itt_message
    cmpx5   count
    tpnz    unmake_message  too many for this chan
append_itt_message:
"               thread new entry to end of queue
    stx1    bb|itt_entry.next_itt_relp,0
    tra 0,7

unmake_message:
    lca 1,dl        meter ITTE non-usage
    ldx4    bb|itt_entry.origin,1
    tnz um_is_ds
    asa bb|apte.ittes_sent,3
um_is_ds:   asa bp|apte.ittes_got
    asa bb|used_itt
    lda 200,dl
    sta errcode     200 means wakeup not sent (itt_ovfl)

    ldac    bb|itt_free_list    give back itt_entry
    tnz *+2     ok -free list locked
    tra *-2     loop on itt_free_list lock
    eax0    0,au        x0 -> former top free itte
"               put that relp in returned itte
    stx0    bb|itt_entry.next_itt_relp,1
    stx1    bb|itt_free_list
    tra 0,7
itt_overflows:
    drltra  (pxss: ITT overflows) error condition
" at this point something must be done to decongest the ITT
    tra 0,7
"
"Subr called via x6 to set Q to zero if not Iz
"Iz pid must be in A, Iz delta in Q, X3 must -> sender
mm_iz_either:
    cmpa    bb|apte.processid,3
    tze 0,6
mm_iz_rcvr:
    cmpa    bp|apte.processid
    tze 0,6
    eaq 0
    tra 0,6
" 
"come here for various functions during initialization
pw1:
    eppap   pds$apt_ptr,*
    ldac    ap|apte.wait_event  get wait event
    sta tc_data$init_event  save the event

    inhibit on      <+><+><+><+><+><+><+><+><+><+><+>
pi_wait:
    lxl1    prds$processor_tag  get processor tag for masking
    lprpab  scs$mask_ptr,1
    xec scs$read_mask,1 find current mask
    staq    pds$tc_mask and save for return
    ldaq    scs$open_level  open up mask to await interrupt
    xec scs$set_mask,1

    sprisp  pds$last_sp save sp because we will change it to non-PRDS
    eppsp   null,*      

    read_clock
    staq    tc_data$init_wait_time  save time of wait
    
check_pi_event:
    lca 1,dl        for display
    ldq tc_data$init_event
    tze pi_wait_ret notify has occured
    
    ldt =o200,du        about 1/8 second
    inhibit off     <-><-><-><-><-><-><-><-><->
    dis 0
    inhibit on      <+><+<+><+><+><+><+><+><+>

    szn tc_data$init_event  0 => event has occurred
    tze pi_wait_ret
    read_clock      " check for notify-time-out
    sbaq    tc_data$init_wait_time " aq = time waiting
    cmpaq   tc_data$init_wait_timeout  " more than allowed
    tmi check_pi_event  " no -wait some more

pi_wait_ret:
    eppsp   pds$last_sp,*   restore stack pointer
    epbpsb  sp|0        ..
    ldaq    pds$tc_mask must be pwait or page_wait
    xec scs$set_mask,1
    szn tc_data$init_event  did we NTO
    tze pi_wait_ret_con no--event occurred
    szn tc_data$init_timeout_severity   do we want to print NTO message
    tmi pi_nto_no_message   no
    szn tc_data$init_timeout_recurse    are we recursing
    tze pi_call_syserr  no
pi_nto_no_message: 
    stz tc_data$init_event  just notify, don't blow the whistle
    tra pi_wait_ret_con


"   call    syserr (tc_data$init_timeout_severity, "pxss:  notify time out:  event=^w. During init/shutdown.")

pi_call_syserr:
    aos tc_data$init_timeout_recurse
    push
    epplb   pds$last_sp,*
    sprilb  pre_temp
    lda pds$pc_call
    sta pre_temp+2
    epplb   tc_data$init_timeout_severity
    sprilb  arg+2
    epplb   pi_timeout_mess
    sprilb  arg+4
    epplb   tc_data$init_event
    sprilb  arg+6
    epplb   fixed_desc
    sprilb  arg+8
    sprilb  arg+12
    epplb   pi_timeout_desc
    sprilb  arg+10
    ldaq    =v18/6,18/4,18/6,18/0
    staq    arg
    call    syserr$syserr(arg)
    epplb   pre_temp,*
    sprilb  pds$last_sp
    lda pre_temp+2
    sta pds$pc_call
    stz tc_data$init_event
    lca 1,dl
    asa tc_data$init_timeout_recurse
    tra pi_wait_ret_con

pi_timeout_mess:
    aci "pxss:  notify time out:  event=^w. During init/shutdown."
    equ pi_timeout_mess_words,*-pi_timeout_mess
    equ pi_timeout_mess_char,4*pi_timeout_mess_words
pi_timeout_desc:
    vfd 1/1,6/21,5/0,24/pi_timeout_mess_char
"

pi_wait_ret_con: 
    eppsp   pds$last_sp,*   restore machine state
    szn pds$pc_call check if pc wait or not
    tze *+2     don't reset ap for page_fault
    eppap   sp|stack_frame.operator_ptr,*
    tmi nwait_ret       normal wait
    szn pds$pc_call is the a pc call?
    tze page_fault$wait_return no
    tra device_control$pwait_return

nwait_ret:
    rtcd    sp|stack_frame.return_ptr
    inhibit off     <-><-><-><-><-><-><-><-><-><->
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   ADDEVENT -- entry to set up event into APT entry prior
"   to a call to wait.
"   There is no need to lock anything.
"   Call is
"       call pxss$addevent(event)
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

addevent:
    ldq ap|2,*      pick up input event
    tnz *+2     event must be non-zero
    ldq =o707070,dl so force it so
    szn tc_data$wait_enable during initialization ?
    tze pi_add      yes, special code
    eppbp   pds$apt_ptr,*
    stq bp|apte.wait_event  store event in APT entry
short_ret:
    short_return

pi_add:
    stq tc_data$init_event
    tra short_ret

fixed_desc:
    oct 404000000005


"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   DELEVENT -- entry to remove interest in event.
"   Call is
"       call pxss$delevent (event)
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

delevent:
    szn tc_data$wait_enable
    tze pi_notify
    eaa 0
    ldq ap|2,*      pick up event
    eppbp   pds$apt_ptr,*   get ptr to own apte
    stacq   bp|apte.wait_event
    short_return
" 
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   PAGE_WAIT -- special purpose entry to wait for a page.
"
"   This entry is a combination of pxss$addevent and pxss$wait.
"   The argument (wait event) is passed in pds$arg_1.
"   Called by page_fault, returns to page_fault$wait_return to
"   restart a page fault.
"
" " " " " " " " " " " " " " " " " " " " " "" " " " " " " " " "

page_wait:
    stc1    pds$pxss_args_invalid Signal overwriting of temporaries.
    stz pds$pc_call set flag for page_wait, vs. waitp.
    szn tc_data$wait_enable see if during initialization
    tze pw1     yes, special code
    tsx6    init_pxss_save_stack init x7 save stack
pwait:              "COME FROM WAITP
    eppbp   pds$apt_ptr,*   get own apt pointer
    epbpbb  bp|0        set bb to point at base of tc_data
    tsx7    update_te       update times

    tsx6    LOCK_bp     -- Wait --
    szn bp|apte.wait_event  wait?
    tze UNLOCK_bp_unpwait   no -event may have occured
    ldx0    waiting,du  set state to waiting
    tsx7    update_execution_state
    tsx6    UNLOCK_bp       page_wait to call getwk
    lda apte.page_wait_flag,du set flag indicating page wait
    orsa    bp|apte.flags   set flag ON in APT entry
    aos bb|waits
    ldx0    prds$depth  get depth in queue
    aos bb|pfdepth,0    for metering
    tsx7    getwork
    read_clock      " meter time ready after notify
    sbaq    bp|apte.state_change_time
    ldx0    prds$depth  get depth in queue
    adx0    prds$depth  multiply depth by 2
    aos bb|readytime,0
    asq bb|readytime+1,0
unpwait:
    szn pds$pc_call see if page_wait or waitp
    tze page_fault$wait_return page_wait
waitp_return:
    eppsp   pds$last_sp,*   restore pds stack history
    epbpsb  sp|0        makes dvctl happy
    ldaq    prds$+stack_header.stack_begin_ptr reset prds too
    staq    prds$+stack_header.stack_end_ptr ..
    tra device_control$pwait_return

UNLOCK_bp_unpwait:
    tsx6    UNLOCK_bp       if not waiting and APTE locked ..
    tra unpwait
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   WAITP -- General purpose entry to wait for a page control event.
"
"   Called by device_control$pwait, we gain control 
"   masked      at sys level, wired stack, and event in pds$arg_1
"
"   Control is returned to caller of device_control$pwait.
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

waitp:
    stc1    pds$pxss_args_invalid Signal overwriting of temporaries.
    lda 1,dl
    sta pds$pc_call set switch for pds return
    szn tc_data$wait_enable see if in initialization
    tze pw1     yes, do special code
    tsplb   setup       switch stacks
    tra pwait


"


" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   WAIT -- entry called to wait for a predictably short time.
"   Call is
"       call pxss$wait
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

wait:
    szn tc_data$wait_enable in initialization ?
    tze w1      yes, special code
    tsplb   setup_mask
    tsx6    LOCK_me_bp
    szn bp|apte.wait_event  have we been notified yet?
    tze wait_not        yes so return
    ldx0    waiting,du  set state to waiting
    tsx7    update_execution_state
    tsx6    UNLOCK_bp
    aos bb|te_wait
    tsx7    update_te       update times
    tsx7    getwork
wait_returns:
    tra switch_back_pds return to pds stack history

wait_not:
    tsx6    UNLOCK_bp
    tra switch_back

w1: lca 1,dl        get negative flag
    sta pds$pc_call
    tra pi_wait
" 
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   PAGE_NOTIFY -- special porpose entry for notifies on pages
"
"   This entry is like notify except that an extra argument is passed.
"   The extra argument is the device ID of the device whose
"   page transfer has just completed. This is used for latency
"   metering.
"
"   The     event being notified is passed in pds$arg_1.
"   The     device ID is passed in pds$arg_2
"
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

page_notify:
    stc1    dev_signal  flag says special notify
pn2:    stc1    pds$pxss_args_invalid Signal overwriting of temporaries.
    szn tc_data$wait_enable during initialization?
    tze pn1     yes, special code

    eppbb   tc_data$        set ptr to base of tcdata
    tsx6    init_pxss_save_stack init x7 save stack

    aos bb|page_notifies
    tsx6    notify_     go notify
"""""   lxl5    x5      restore x5
    tra notify_return
pn1:
    ldq pds$arg_1       see if we are waiting for the event being notified
    cmpq    tc_data$init_event  is it what we're waiting for?
    tnz notify_return   no, return
    stz tc_data$init_event  yes, reset event
notify_return:
    tra page$notify_return  
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   NOTIFY -- entry to notify that an event has happened.
"   Call is
"       call pxss$notify(event)
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

notify:
    szn tc_data$wait_enable in initialization ?
    tze pi_notify       yes, special code
    tsx6    setup_
    ldq ap|2,*      pick up event to notify
    tnz *+2     can't allow zero, so use 707070
    ldq =o707070,dl
    stq pds$arg_1       save in PDS
    tsplb   setup_check switch stacks and lock
    arg 0,6
    stz dev_signal  set flag for normal return
    aos bb|notifies
"
"Come here to notify event which is in pds$arg_1
notify_:
    stx6    x5      save index 6 (used by get_processor )
    tsx6    READ_LOCK       notify freezes threads
    eax2    bb|eligible_q_head  X2 -> eligible_q_head
    eax5    0       USELESS NOTIFY?
    ldq pds$arg_1       Put event in Q-reg and stack
    stq tmp_event
nfy_loop:
    ldx2    bb|apte.thread,2    go to next entry
    szn bb|apte.flags,2 stop at sentinel
    tmi nfy_ret
    cmpq    bb|apte.wait_event,2 check current APT for same event
    tnz nfy_loop        not equal, skip
"
"   Fall thru here if must notify this process
    tsx6    LOCK_x2
    lcx0    apte.page_wait_flag+1,du turn off page wait flag
    ansx0   bb|apte.flags,2
    stz bb|apte.wait_event,2
    eax5    1,5     USELESS NOTIFY?
    lxl0    bb|apte.state,2 make sure dont change running state
    cmpx0   waiting,du
    tnz nfy_not_waiting not waiting, dont change state
    ldx0    ready,du        set state to ready
    eppbp   bb|0,2      bp must be set for update..
    tsx7    update_execution_state
    tsx6    UNLOCK_x2
    eax7    nfy_restor  set ret addr for get_(idle_)processor
    szn bb|gp_at_notify see which flavor gp
    tze get_idle_processor
    tra get_processor
nfy_restor:
    ldq tmp_event       Restore event to Q
    tra nfy_loop        continue scan
nfy_not_waiting:
    tsx6    UNLOCK_x2
    tra nfy_restor  restor back
nfy_ret:
    eax5    0,5     USELESS NOTIFY?
    tnz *+3     USELESS NOTIFY?
    stq bb|notify_nobody_event  USELESS NOTIFY?
    aos bb|notify_nobody_count  USELESS NOTIFY?
    tsx6    UNLOCK      notify is done
    ldx6    x5      restore x6
    szn dev_signal  check return flag
    tnz 0,6     return to caller
    tra switch_back

pi_notify:
    ldq ap|2,*      get event
    tnz *+2     if non-zero, OK
    ldq =o707070,dl otherwise use special coded event
    cmpq    tc_data$init_event
    tnz short_ret       not the right one
    stz tc_data$init_event
    short_return
" 
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   PTL_WAIT -- wait for  page table lock
"
"   special cased to avoid cluttering up page_wait even further.
"
"   Note that the per APTE lock protects processes interest in
"   ptl_wait_ct.  ptl_wait_ct is >= num of (unlocked) processes in the
"   ptlocking state.  It is greater only temporarily, while
"   some process is making sure a notify is not lost.
"
" " " " " " " " " " " "  " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

ptl_wait:
    stc1    pds$pxss_args_invalid
    stz pds$pc_call set return switch
    eppbb   tc_data$
    tsx6    init_pxss_save_stack

ptlw_:  szn sst$+sst.ptl    quick check on ptl
    tze ptlw_ez     not locked - meter and retn
    tsx6    LOCK_me_bp
    ldx0    ptlocking,du    go ptlocking
    tsx7    update_execution_state now I cannot miss notify
    aos sst$+sst.ptl_wait_ct guar notify next time ptl unlocked
    szn sst$+sst.ptl    see if still locked
    tze ptlw_not        no - dont wait
    tsx6    UNLOCK_bp
    tsx7    update_te
    aos bb|ptl_waits    meter these
    tsx7    getwork
ptlw_ret:   szn pds$pc_call test return switch
    tze page_fault$ptl_wait_return return to locking code
    tra waitp_return    take fancy return to dvctl trylock

ptlw_not:   lca 1,dl
    asa sst$+sst.ptl_wait_ct dont notify on my account
    ldx0    running,du  like delevent ..
    tsx7    update_execution_state
    tsx6    UNLOCK_bp
ptlw_ez:    aos bb|ptl_not_waits    meter this window
    tra ptlw_ret

dvctl_retry_ptlwait:
    lda 1,dl
    sta pds$pc_call set return switch
    tsplb   setup
    tra ptlw_       join common ptlwait code

"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   PTL_NOTIFY -- notify one process that ptl is unlocked
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

ptl_notify:
    stc1    pds$pxss_args_invalid
    eppbb   tc_data$
    tsx6    init_pxss_save_stack
    tsx6    READ_LOCK       ptl_notify freezes threads

    lda bb|eligible_q_head
ptln_loop:
    ldaq    bb|apte.thread,au
    tra ptln_tv,ql*

    arg ptln_ret        sentinel
ptln_tv:    arg DRL_empty_apte  empty
    arg ptln_loop       running
    arg ptln_loop       ready
    arg ptln_loop       waiting
    arg DRL_blocked_apte    blocked
    arg DRL_stopped_apte    stopped
    arg ptln_found_ptlocking

" This is the place where jump indirects through tables come when they feel
" a need to punt. We lose a few hreg entries, but not so many as a arg *,*!

DRL_empty_apte:
    drltra  (pxss: untenable empty APTE)
DRL_blocked_apte:
    drltra  (pxss: untenable blocked APTE)
DRL_stopped_apte:
    drltra  (pxss: untenable stopped APTE)

ptln_found_ptlocking:
    szn sst$+sst.ptl    is ptl actually unlocked now?
    tnz ptln_ret        no - abort ptln

    lxl2    bb|apte.thread,au   make X2 -> ptlocking process
    tsx6    LOCK_x2     ptln locks to test and set state
    lxl0    bb|apte.state,2
    cmpx0   ptlocking,du
    tnz ptln_not_ptlocking

    lca 1,dl
    asa sst$+sst.ptl_wait_ct one fewer waiting for ptl
    eppbp   bb|0,2      set bp for ues
    ldx0    ready,du
    tsx7    update_execution_state
    tsx6    UNLOCK_x2

    eax7    ptln_ret        set ret addr for get_(idle_)processor
    szn bb|gp_at_ptlnotify  see which flavor gp
    tze get_idle_processor
    tra get_processor

ptln_ret:   tsx6    UNLOCK
"""""   lxl5    x5      ??????
    tra core_queue_man$ptl_notify_return

ptln_not_ptlocking:
    tsx6    UNLOCK_x2
    tra ptln_loop

"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   UPDATE_TE -- procedure to update the virtual cpu time used by
"   the running process into "te".
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

update_te:
    eppbp   pds$apt_ptr,*   get own apt pointer
    epbpbb  bp|0        set bb to point at base of tc_data
    szn pds$vtime_count
    tpl te_vtime_1
    read_clock  
    sbaq    pds$cpu_time
    tra te_vtime_2
te_vtime_1:
    ldaq    pds$time_v_temp
te_vtime_2:
    sbaq    pds$virtual_delta
    sbaq    pds$virtual_time_at_eligibility
    stq bp|apte.te
    tra 0,7



" 
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   USAGE_VALUES  -- procedure to return the total page faults
"   for this process as well as the total cpu time this process
"   has been charged with.
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

usage_values:
    szn tc_data$wait_enable see if during initialization
    tze ret_zero        if so return zeros
    inhibit on      <+><+><+><+><+><+><+><+><+><+><+><+><+><+><+><+><+>
    read_clock
    sbaq    pds$cpu_time    compute virtual time
    sbaq    pds$virtual_delta   convert to virtual cpu time
    staq    ap|4,*      return value
    ldq pds$page_waits  also return page waits
    stq ap|2,*      return value
    short_return
    inhibit off     <-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><->

ret_zero:
    fld 0,dl        zero a-q
    staq    ap|4,*      return time of zero
    stz ap|2,*      return zero page_waits
    short_return
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   SORT_IN -- procedure to sort a process into the ready list at 
"   the appropriate spot depending on his updated ti value.
"   bp must point to the APT entry for the process to be sorted 
"   in.
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

sort_in_before:
    stc1    before      here to sort in before those of same ti
    tra *+2
sort_in:
    stz before
    aos bb|schedulings
    ldx0    apte.interaction,du check interaction switch
    canx0   bp|apte.flags
    tze sort_no_int no interaction
    tsx0    setup_int       boost priority
    lcx0    apte.interaction+1,du
    ansx0   bp|apte.flags   turn off interaction bit

sort_no_int:
    lda bp|apte.ti
    cmpa    bp|apte.timax   must always be less than timax
    tmi *+2     ok, use new value
    lda bp|apte.timax   must use max value
    sta bp|apte.ti

    ldx0    bp|apte.flags
    canx0   apte.realtime_burst,du realtime boost
    tze sort_in_again   no
    tsx0    setup_io_realtime   yes
    tra realtime_sort
sort_in_again:
"old_assume_bp_not_eligible
"old_assume_bp_wct_index
    ldx1    bp|apte.wct_index   here to put in right rdy_q
    szn bb|wcte.realtime,1  if realtime
    tnz realtime_sort   sort into realtime_q
    szn bb|deadline_mode    else if not percenting
    tnz virtual_sort    then put in interactive_q
    lda bb|wcte.interactive_q_word,1    See if interactive queue is
    cana    wcte.interactive_q,du     enabled for this WC
    tze ti_loop     Not enabled
"
"   here to sort into workclass or interactive queue by ti
ti_sort:    ldx4    bp|apte.ti  put ti in X4 for sort
    tnz ti_loop     sort into X1 -> wc_q
    szn bp|apte.ts  also if ts not zero
    tnz ti_loop     sort into X1 -> wc_q
    szn bb|int_q_enabled    also if int_q turned off
    tze ti_loop     sort into X1 -> wc_q
    eax1    bb|interactive_q    else direct to tail of int_q
    tra thread_him_in
ti_loop:
    ldx1    bb|apte.thread,1    chase to next
    szn bb|apte.sentinel,1  put before sentinel
    tmi thread_him_in
    cmpx4   bb|apte.ti,1    compare to this ti
    tpnz    ti_loop     if greater, go deeper
    tmi thread_him_in   if less, put before
    szn before      if equal and told to put before
    tnz thread_him_in   then put before
    tra ti_loop     else go deeper
"
"   various deadline sorts follow
realtime_sort:
    ldx0    apte.realtime_burst,du  mark as realtime
    orsx0   bp|apte.flags
    eax1    bb|realtime_q   here to put in realtime_q
    tra deadline_sort
virtual_sort:
    eax1    bb|interactive_q    int_q has vir deadlines
deadline_sort:
    ldaq    bp|apte.deadline    here for general deadline sort
ds_loop:
    ldx1    bb|apte.thread,1    chase to next
    szn bb|apte.sentinel,1  put before sentinel
    tmi thread_him_in
    cmpaq   bb|apte.deadline,1  compare to this deadline
    tpl ds_loop     sooner => fall thru to thread before
"
"Subroutine to thread unthreaded APTE at bp|0 before that at bb|0,1.
"
thread_him_in:
    szn tc_data$apt_lock    ASSUME write_locked
    drlmi   (pxss: APT not locked) ASSUME write_locked
    szn bp|apte.thread  ASSUME bp_unthreaded
    drlnz   (pxss: thread_him_in already threaded) ASSUME bp_unthreaded
    eax1    0,1     ASSUME x1_nonzero
    drlze   (pxss: thread_him_in x1 zero) ASSUME x1_nonzero
    lxl4    bb|apte.thread,1    thread new entry in here
    drlze   (pxss: thread_him_in x4 zero) ASSUME x4_nonzero
    cmpx1   bb|apte.thread,4    ASSUME x4->apte.fp = x1
    drlnz   (pxss: thread_him_in x4->apte.fp ^= x1) ASSUME x4->apte.fp = x1
    lxl0    bp|apte.state
    cmpx0   ready,du        ASSUME apte.state = "ready"
    drlnz   (pxss: apte.state ^= ready)
    eax0    bp|0
    drlze   (pxss: thread_him_in x0 zero) ASSUME x0_nonzero
    sxl0    bb|apte.thread,1
    stx0    bb|apte.thread,4
    sxl4    bp|apte.thread
    stx1    bp|apte.thread
    tra 0,7
" 
" " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   THREAD_IN_IDLE -- entry to thread an idle process into the
"   ready list.  Called during initialization and reconfiguration.
"
"   call pxss$thread_in_idle(apt_ptr)
"
"   Where:
"
"       apt_ptr is an its pointer to an idle process apt entry
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " "

thread_in_idle:
    tsx6    setup_
    ldaq    ap|2,*      save apt_ptr in PDS
    staq    pds$arg_1
    tsplb   setup_check switch stacks and lock
    arg 0,6
    tsx6    WRITE_LOCK  thread_in_idle rethreads
    eppbp   pds$arg_1,* get pointer to APT entry
    tsx7    unthread        thread entry out of any list it's in
    ldx1    bb|eligible_q_tail
    tsx7    thread_him_in   thread into list
    tsx6    UNLOCK      thread_in_idle is done
    tra switch_back
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   UNTHREAD_APTE -- entry to unthread an APTE from whatever
"   queue it's in.  Called by stop_cpu during reconfiguration.
"
"   call pxss$unthread_apte (apt_ptr)
"
"   Where:
"       apt_ptr is a pointer to the APTE to be unthreaded.
"
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

unthread_apte:
    tsx6    setup_
    ldaq    ap|2,*
    staq    pds$arg_1
    tsplb   setup_check
    arg 0,6
    tsx6    WRITE_LOCK  pxss$unthread_apte

    eppbp   pds$arg_1,*
    tsx7    unthread
    tsx6    UNLOCK      pxss$unthread_apte
    tra switch_back
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   GET_PROCESSOR -- procedure to award a processor (via
"   pre_emption of a lower priority process).
"   First check idle processes, then check eligible processes starting
"   with lowest priority eligible process).
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   GET_IDLE_PROCESSOR -- procedure to award a processor via
"   pre-emption of an idle process.  This procedure should be
"   called instead of get_processor when the process for whom we
"   are pre-empting may not even be eligible yet.
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

get_idle_processor:
    eax0    1       just check idle processes
    ldx3    bb|apte.wct_index,2 fall thru to full gp if realtime
    szn bb|wcte.realtime,3
    tze gp_init     go check idle queue
get_processor:
    eax0    2       check both idle and elig queues

gp_init:    lda bb|apte.thread,2    pre-empting on behalf of X2
    sta temp        save (unique) threads for compares
    lda bb|idle_tail    start with idle processes
    aos bb|gp_start_count   For getwork/get_processor window
"
"All set up.  Now for the main loop.  It will be executed over the
"Idle processes first.  If no Idle process is running then if the
"XR0 is > 1 (ie: came in at get_processor entry,
"not get_idle_processor) then the loop will be executed over the
"elig processes.
"
gp_loop:    ldaq    bb|apte.thread,al   go higher in queue
    cmpa    temp        is this process which was notified?
    tnz gp_tv,ql*       no - branch on state
    tra gp_return       yes - give up and return

    arg check_eligible  -1 => sentinel
gp_tv:  arg gp_loop     gp found empty (ok-may be idle)
    arg gp_found_running
    arg gp_loop     gp found ready
    arg gp_loop     gp found waiting
    arg DRL_blocked_apte    gp found blocked
    arg DRL_stopped_apte    gp found stopped
    arg gp_loop     gp found ptlocking

gp_found_running:
    lxl3    bb|apte.thread,au   extract addr from next's backptr
    tsx6    LOCK_x3     gp locks
    ldq bb|apte.flags,3 grab fresh copy of flags
    canq    apte.pre_empted,du  have we pre-empted it before?
    tnz gp_skip     yes, skip it
    lda apte.pre_empted+apte.pre_empt_pending,du turn on pre_empted bit
    orsa    bb|apte.flags,3
    ldq bb|apte.flags2,3    get processor tag
    anq apte.pr_tag_mask,dl
    cmpq    prds$processor_tag  this cpu?
    tze gp_this_cpu dont cioc if so
    cioc    scs$cow_ptrs,ql*
    tsx6    UNLOCK_x3       gp unlocks
    tra gp_return

gp_this_cpu:
    tsx6    UNLOCK_x3       gp unlocks
    lda 1,dl        set alarm to r1
    sta pds$alarm_ring
    lra pds$alarm_ring
    tra gp_return

gp_skip:    tsx6    UNLOCK_x3
    tra gp_loop     have not clobbered AL ( -> next)

check_eligible:
    eax0    -1,0        need check the eligibles?
    tze gp_return       no - return to caller

    lda bb|eligible_q_tail  now check eligibles
    tra gp_loop
gp_return:
    aos bb|gp_done_count    For getwork/get_processor window
    tra 0,7     gp ret to caller
" 
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   FIND_NEXT_ELIGIBLE -- the policy implementing part of the
"   scheduler.  This code could be in-line in getwork but is
"   separated for clarity. It is entered at find_next_eligible
"   from getwork if no eligible process can be run.  It is entered
"   with the traffic controller locked for multiple readers but
"   must run with the exclusive lock allowing rethreading and awarding
"   eligibility.  It will return to getwork with x2 -> newly elig
"   or will return to find idle. In either case before returning
"   the lock will be changed to allow concurrency.
"   This code will also be entered at AWARD with the write lock set,
"   from the part of getwork that decides to award eligibility
"   to process with realtime deadlines which are past.

"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
find_next_eligible:
    tsx6    UNLOCK      fne must unlock read before write
    tsx6    WRITE_LOCK  fne rethreads, awards elig
"
"If there are any processes which have just interacted,
"we must run them immediately.  So check the interactive_queue.
"If we are in deadline mode the interactive queue contains most processes
"and also some special conditions must be fulfilled.
"

    eax2    bb|interactive_q
    stz temp2       no work class restrictions so far
lr_loop:
    ldx2    bb|apte.thread,2    step to next process.
    szn bb|apte.sentinel,2  If none then scan wc queues.
    tmi lr_wcq      Look in workclass queues
    szn bb|deadline_mode    If deadline mode make checks.
    tze test_award  Else return this X2 to getwork.

    ldx1    bb|apte.wct_index,2 Check if limit on n elig
    ldq bb|wcte.maxel,1
    tze lr_e_ok     OK if no limit.
    cmpq    bb|wcte.nel,1   Else compare to limit.
    tpnz    lr_e_ok     Below limit.
    stc1    temp2       Flag work class restriction
    tra lr_loop     And go deeped
lr_e_ok:
    lda bb|max_batch_elig   Limit on batch?
    tze test_award  No.
    lxl1    bb|apte.flags2,2    Yes.
    canx1   apte.batch,du   Is this process batch?
    tze test_award  No, limit doesn't matter.
    cmpa    bb|num_batch_elig   Yes, check limit.
    tpnz    test_award  OK, within limit.
    stc1    temp2       Flag work class restriction
    tra lr_loop     Go deeper.

lr_wcq:
"
"Nobody found in interactive/virtual_deadline queue.  We must
"update the credits in each workclass before scanning the workclass queues.
"
    szn bb|deadline_mode
    tze sc_loop     not dmode => scatter credits
    stz bb|credit_bank  clear cell prvents ovfl
    tra sc_done

sc_loop:
    lda bb|credit_bank  Are there credits to scatter?
    tmi sc_done     No.
    stz bb|credits_scattered None scattered yet.
    ldq bb|telast       Credits always < 4*telast
    qls 2
    ldx1    bb|min_wct_index    Start at first work class.

sc_wc_loop:
    szn bb|wcte.realtime,1  Is this wc getting some percent?
    tnz sc_check        No, go to next wc. after clipping
    lda bb|wcte.minf,1  Yes, give credits.
    asa bb|wcte.credits,1
    asa bb|credits_scattered Note we scattered some.
sc_check: 
    szn bb|wcte.credits,1   don't let credits go negative
    tpl *+2
    stz bb|wcte.credits,1
    cmpq    bb|wcte.credits,1   Make sure credits < 4*telast
    tpl sc_next     OK.
    stq bb|wcte.credits,1   CREDIT SINK
sc_next:
    eax1    size_of_wct_entry,1 Move to next work class.
    cmpx1   bb|max_wct_index    Done all?
    tmoz    sc_wc_loop  more wc to do.

    lca bb|credits_scattered Did we scatter any?
    tze sc_done     No. Don't loop back!
    asa bb|credit_bank  Yes. Bookkeep and
    tra sc_loop     loop back to scatter more.
sc_done:

"
"   Now scan the workclass queues to find the most worthy workclass
"   which has a non-empty queue.  In this process, governing credits
"   are distributed to governed work classes.  Any governed work
"   class whose governing credits are negative will not be
"   considered for eligibility.
"

    ldx2    -1,du       Preset to none-found.
    ldx1    bb|min_wct_index    Start at first work class.
    lda =o400000,du Anybody can beat this.
    sta bb|best_credit_value

    eax0    -1      preset to do-not-pass-out-gv-credits
    szn bb|governing_credit_bank
    tmi fne_loop        none to pass out
    lda bb|governing_credit_bank    pass out, decrementing bank
    sba bb|credits_per_scatter
    ars 1           allow to grow to 2*credits_per_scatter
    cmpa    bb|credits_per_scatter  limit growth in the bank
    tmoz    *+2
    lda bb|credits_per_scatter  (must be S&L)
    als 1
    sta bb|governing_credit_bank
    eax0    0       pass-out-gv-credits
    
fne_loop:
    lda bb|wcte.governed_word,1 is this W.C. governed
    cana    wcte.governed,du
    tze fne_not_governed    No
    eax0    0,0     governing credits to pass out
    tmi fne_no_gv_credits   no
    lda bb|wcte.maxf,1  credits for this W.C. per scatter
    asa bb|wcte.governing_credits,1
fne_no_gv_credits:
    lda bb|gv_integration   limit of abs value of gv credits
    cmpa    bb|wcte.governing_credits,1
    tpl *+2
    sta bb|wcte.governing_credits,1
    szn bb|wcte.governing_credits,1 is this W.C. in the hole?
    tpl fne_not_governed    no - can consider for eligibility
    neg 0       areg = -limit ov abs value of gv credits
    cmpa    bb|wcte.governing_credits,1 there's a limit on the hole
    tmi *+2     negative but not bankrupt 
    sta bb|wcte.governing_credits,1 minimum value
    cmpx1   bb|wcte.thread,1    Th to self => none ready
    tze fne_try_next
    stc1    temp2       flag work class restriction
    tra fne_try_next

fne_not_governed: 
    cmpx1   bb|wcte.thread,1    Th to self => none rdy.
    tze fne_try_next
    szn bb|wcte.credits,1   Credits never left negative
    tpl *+2
    stz bb|wcte.credits,1   CREDIT SOURCE

    ldx3    bb|wcte.thread,1
    lca bb|telast       maximize credits - min (ti, 2*telast)
    als 1
    cmg bb|apte.ti,3    deal with abs values
    tmi *+2
    lca bb|apte.ti,3

    ada bb|wcte.credits,1
    cmpa    bb|best_credit_value See if this is best sofar.
    tmi fne_try_next    Wasn't, move to next.

    sta bb|best_credit_value Was, remember value.
    eax2    0,3     remember the champ

fne_try_next:
    eax1    size_of_wct_entry,1 Move to next work class.
    cmpx1   bb|max_wct_index    If any.
    tmoz    fne_loop
    eax2    0,2     Neg=> nobody
    tpl test_award  See if process fits in core.
    tra recheck_real    Continue looking for candidate.

"
"Come here if no processes found in int/vird queue or in
"the workclass queues.  We determine whether any process is present in the
"realtime queue.  If so such a process will be awarded eligibility subject
"to the usual constraints even though its deadline has not
"arrived yet.
"
recheck_real:
    ldx2    bb|realtime_q   REALTIME AWARD?
    szn bb|apte.sentinel,2
    tmi fne_fail
"
"Arrive here with x2 pointing at a candidate for eligibility.
"A few more checks are made to determine if eligibility will
"actually be awarded.
"
test_award:
    lda bb|apte.flags,2
    cana    apte.dbr_loaded,du
    tze *+3     dbr not loaded ok to award elig
    cmpx2   pds$apt_ptr+1
    tnz fne_fail        dbr loaded ok only if this cpu

    lxl0    bb|n_eligible   get number of eligible processes
    cmpx0   bb|min_eligible Below min eligible?
    tmi award       Yes, make eligible.
    cmpx0   bb|max_eligible At max eligible?
    tpl fne_fail        Yes, go idle.
    cmpx0   bb|max_max_eligible
    tpl fne_fail
    ldq bb|ws_sum       See if it fits.
    adq bb|apte.ws_size,2
    cmpq    sst$+sst.nused
    tpl fne_fail        no, go idle.
"
"Here to award eligibility to x2-> apte.  Arrive here
"either by falling through above code or directly from getwork
"if a process 's realtime deadline has arrived.
"
award:
    aos bb|n_eligible   increment count of eligible s
    lxl1    bb|apte.flags2,2    DIGS
    canx1   apte.batch,du
    tze *+2
    aos bb|num_batch_elig
    ldx1    bb|apte.wct_index,2
    aos bb|wcte.nel,1   and per wc count
    ldx0    apte.eligible,du
    ersx0   bb|apte.flags,2
    ldq bb|apte.ws_size,2   ws_sum = ws_sum + ws_size,2
    anq -1,dl       Leave only ws estimate
    asq bb|ws_sum
"
"put the newly elig process in the proper place in elig queue
"note bp is fudged for sort subr's, then reset
"
    eppbp   bb|0,2      Set bp to process of interest
    tsx7    unthread        Remove from ready queue
    eax1    bb|eligible_q_tail  Assume put at eltail.

    ldx0    bb|apte.flags,2
    canx0   apte.realtime_burst,du  Normal process?
    tze put_in_el_q Yes, put at tail of el queue.
    lcx0    apte.realtime_burst+1,du Reset flag
    ansx0   bb|apte.flags,2

    ldx1    bb|eligible_q_head  Could sort by deadline here
    stz depth       Reset depth.
put_in_el_q:
    tsx7    thread_him_in
    ldq AWARD_ELIGIBILITY,dl
    tsx7    meter_response_time$tc  response transition
    eppbp   pds$apt_ptr,*   Restore bp -> old_user
    tsx6    LOCK_x2     Claim this apte now
    tsx6    WRITE_TO_READ   shift lock to read before return

    ldx1    bb|apte.wct_index,2 Determine work class.
    lda bb|apte.temax,2 Decrement wc credits in advance
    tnz *+2
    lda bb|process_initial_quantum  Must be first time thru
    sta bb|apte.saved_temax,2   Save to compensate later
    neg
    asa bb|wcte.credits,1
    ldq bb|wcte.governed_word,1 Is wc governed
    canq    wcte.governed,du
    tze *+2     No
    asa bb|wcte.governing_credits,1
    aos bb|wcte.eligibilities,1 Meter elig awarded.

    ldq bb|apte.ts,2    see if first time since wakeup
    adq bb|apte.ti,2    continue with check ...
    tnz no_response non-zero means not first time
    read_clock      " meter response time
    sbaq    bb|apte.state_change_time,2 get time this process was ready
    adaq    bb|response_time    add in to total meter
    staq    bb|response_time
    aos bb|response_count   count number of times we added in
no_response:
    tra gw_ck_ready Success return from fne

fne_fail:
    tsx6    WRITE_TO_READ
    tra find_idle       Failure return from fne


"
" " " " " " " " " " " " " " " " " " " "  " " " " " " " " " " " " " " "
"
"   GETWORK -- procedure which is invoked when the running 
"   process is ready to give the processor to another process.
"   When it is invoked bb->bp must point into tc_data.
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

getwork:
    stc1    pds$pxss_args_invalid Signal overwriting of temporaries.
    stz pds$post_purged reset flag indicating purge is done
    read_clock      " meter getwork time
    staq    getwork_temp    save for user being switched to
    sxl7    bp|apte.savex7  save x7 in APT entry
    aos bb|depth_count
    lcx0    apte.pre_empted+apte.pre_empt_pending+1,du turn off pre-empted flags
    ansx0   bp|apte.flags

gw_retry:
"
"   The following code, checked each time through the traffic controller, checks to see
"   if the running processor is to be deleted. If so, it branches to the
"   appropriate code which ultimately DISes.
"   1. Force idle process to run on this cpu.
"   2. Rather than returning from getwork, transfer to DIS-ing code.
"
    lxl1    prds$processor_tag  get our own processor tag
    lda scs$processor_data,1 get our control word from SCS
    cana    processor_data.delete_cpu,du
    tnz find_idle_for_delete
"
"   End of reconfiguration tests
"
    read_clock      " get time in AQ for several tests

    cmpaq   bb|next_alarm_time  go boost priority if time
    tpl alarm_timer ..

    cmpaq   bb|nto_check_time   go check notify_timeouts if time
    tpl nto_check

    ldx2    bb|realtime_q   maybe realtime award?
    szn bb|apte.sentinel,2
    tmi gw_lock_init_loop   nobody
    cmpaq   bb|apte.deadline,2
    tmi gw_lock_init_loop   Not yet

    tsx6    WRITE_LOCK  probable realtime award
    lxl0    bb|n_eligible
    cmpx0   bb|max_max_eligible
    tpl no_real
    ldx2    bb|realtime_q   REALTIME AWARD?
    szn bb|apte.sentinel,2
    tmi no_real
    read_clock  
    cmpaq   bb|apte.deadline,2
    tmi no_real     Delay him for now.

    lda bb|apte.flags,2
    cana    apte.dbr_loaded,du
    tze award       dbr not loaded ok to award elig
    cmpx2   pds$apt_ptr+1
    tze award       dbr loaded ok only if this cpu
no_real:
    tsx6    WRITE_TO_READ   cant make realtime elig so shift to readlock
    tra gw_init_loop

gw_lock_init_loop:
    tsx6    READ_LOCK
gw_init_loop:
    lda bb|gp_done_count    If gp occur we must not idle
    sta temp1       So we remember the count
    lda bb|eligible_q_head  ELIGIBLE to RUN?
    ldx1    -1,du       keep depth in X1

gw_loop:
    ldaq    bb|apte.thread,au   go to next entry via AU
    eax1    1,1     increase depth
    tra gw_tv,ql*       dispatch on state in QL

    arg find_next_eligible  state was -1 ie sentinel
gw_tv:  arg DRL_empty_apte  found empty
    arg gw_loop     state was running so go deeper
    arg gw_found_ready
    arg gw_loop     state was waiting so go deeper
    arg DRL_blocked_apte    found blocked
    arg DRL_stopped_apte    found stopped
    arg gw_loop     found ptlocking



gw_found_ready:
    ldx2    bb|apte.thread,al   extract ready's addr from prev's fp
    tsx6    LOCK_x2     gw must make sure x2 is ready etc
    sxl1    depth       remember depth
gw_ck_ready:
    lda bb|apte.flags,2 pick up flags&state from locked apte
    eax0    0,al        put state in X0
    cmpx0   ready,du        really ready?
    tnz gw_cant_run not really ready
    cana    apte.dbr_loaded,du  Is dbr_loaded?
    tze gw_dbr_ok       no - ok to run
    cmpx2   pds$apt_ptr+1   yes - Is this self?
    tnz gw_cant_run_dbrl    not self - must not touch
gw_dbr_ok:
    cana    apte.loaded,du  is this process loaded?
    tze load_him        no, load the process

    lda bb|apte.procs_required,2    see whether this process
    ana apte.procs_required_mask,du can run on this CPU
    ana prds$processor_pattern
    tnz gw_can_run      OK to run

gw_cant_run:
    tsx6    UNLOCK_x2       unlock the apte
    lda bb|apte.thread,2    repair AU
    tra gw_loop     go loop to next
gw_cant_run_dbrl:
    lca 1,dl        force gw retry if we idle
    asa temp1       by spoiling gw_gp_window test
    tra gw_cant_run

gw_can_run:
"
"We have decided to run this process, but will retry getwork if this
"process is idle and if there has been a get_processor in progress while
"we were chasing down the eligible queue. Strictly speaking, we should retry
"even if the found process is not idle -- but who cares.  Strictly speaking
"we need not retry (cannot lose a get_processor) unless BOTH a process has
"been readied AND a get_processor has been in progress DURING this getwork.
"
    eppbp   bb|0,2      processor addevent!
    ldx0    running,du  mark as running
    tsx7    update_execution_state

    lda bb|gp_start_count
    ssa temp1       temp1 is nonzero if done(t0) ^= start(t1)

gw_can_really_run:
    tsx7    compute_virtual_clocks
    tsx7    set_newt        calculate the time to run
    lxl0    depth       set depth meter
    cmpx0   max_depth-1,du  depth can't be bigger than max
    tmi *+2
    ldx0    max_depth-1,du
    stx0    prds$depth
    eppap   pds$apt_ptr,*   get pointer to own APT entry
    lda ap|apte.flags   see if eligible
    cana    apte.eligible,du    ..
    tnz switch_process  if eligible, don't reset count
    stz pds$number_of_pages_in_use
switch_process:
    spriap  apt_ptr     save pointer to last user to run
    eppbp   bb|0,2      make bp -> our APTE

" " " " " " " " " " " " " " " " " " " " "
"                     "
"   P R O C E S S   S W I T C H I N G   "
"               "
"        D O N E    H E R E     "
"               "
" " " " " " " " " " " " " " " " " " " " "
    ldx0    lp|prds_link    pick up segno of prds
    adlx0   lp|prds_link    multiply by two (add it in again)
    sbar    pds$base_addr_reg   save base address reg for process
    ldt prds$last_timer_setting and load with new value
    inhibit on      <+><+><+><+><+><+><+><+><+><+><+><+>
    ldaq    dseg$+0,0       pick up prds SDW address
    ldbr    bp|apte.dbr load the dbr
    staq    dseg$+0,0       store prds SDW in new DSEG

    spribp  prds$apt_ptr    mark process running this cpu
    lda prds$processor_tag  set proc tag for
    era bp|apte.flags2
    ana apte.pr_tag_mask,dl
    tze *+2
    cams    4       clear cache if different cpu
    ersa    bp|apte.flags2
    eppab   prds$mode_reg_enabled
    lca mr.enable_hfp+1,dl  is hex fp enabled for this process?
    ana prds$mode_reg
    szn pds$hfp_exponent_enabled
    tze *+2
    ora mr.enable_hfp,dl
    sta prds$mode_reg
    ora mr.enable_mr+mr.enable_hist,dl  enable mode reg and enable hist regs
    sta ab|0
    lcpr    ab|0,04
    inhibit off     <-><-><-><-><-><-><-><-><-><-><-><->

    lda ap|apte.flags   OLD USER TO BE UNLOADED?
    cana    apte.eligible+apte.always_loaded,du Loadedness protected?
    tnz dont_unload yes - skip unload
    cana    apte.loaded,du  Currently loaded?
    tze dont_unload no - can't unload

    lcx0    apte.loaded+1,du    turn off loaded bit
    ansx0   ap|apte.flags

    lda ap|apte.asteps  pick up both asteps
    lcq ptw.wired+1,dl  set bit for turnoff
    ansq    sst$+aste_size,au   unwire one
    ansq    sst$+aste_size,al   unwire other
    lcq 2,dl        unwired total of two pages
    asq sst$+sst.wired  keep pc counter up to date
dont_unload:
"
"At this point we can pass the apte.dbr_loaded bit to the new user.
"This simple bit prevented anything interesting from happening
"to old user from the time he left the running state until now.
"Old user cannot have regained elig, except on this cpu, if it was lost.
"Old user cannot be running on any other cpu.
"Old user not subject to load/unload race: gw not go to load_him if dbr_loaded
"If old lost elig then old not in elig_q then old not being loaded.
"There is a window in the opposite direction, analogous to the gp/gw race:
"Old user is HIDDEN from getwork on other cpu's.  It is possible for
"another cpu to go idle because this cpu has hidden old_user.  This possibility
"is prevented by forcing a gw_gp_window retry of getwork if
"a processor is about to go idle but has seen and skipped
"a ready process because that process had it dbrloaded
"on another processor.
"
    lcx0    apte.dbr_loaded+1,du turn off dbr_loaded bit for old user
    ansx0   ap|apte.flags
    ldx0    apte.dbr_loaded,du  turn on dbr_loaded bit for new user
    orsx0   bp|apte.flags

    tsx6    UNLOCK_bp       gw unlocks new user
    tsx6    UNLOCK

stop_check:
    eppbp   apt_ptr,*       OLD USER STOPPED?
    lxl0    bp|apte.state   check for stopped
    cmpx0   stopped,du
    tze stop_check_ jump out_of_line
end_stop_check:

cpu_monitor_check:
    szn bp|apte.cpu_monitor OLD USER MONITOR TIME?
    tnz cpu_monitor_    maybe time now
end_cpu_monitor:

cput_check:
    eppbp   pds$apt_ptr,*   NEW USER CPUT?
    lda bp|apte.flags   see if idle
    cana    apte.idle,du    yes, skip timer check
    tnz end_cput_check  ..
    szn pds$timer_time_out+1 see if non-zero time exists
    tze end_cput_check  no, continue with getwork
    ldaq    bp|apte.virtual_cpu_time see if process is over time limit
    cmpaq   pds$timer_time_out
    tmi end_cput_check  no, continue with getwork
    fld 0,dl        zero the time out value
    staq    pds$timer_time_out
    ldaq    pds$timer_channel   time up, check if ev chan is zero
    tnz cput_wakeup it's non-zero, need full wakeup
    lda sys_info$cput_mask
    orsa    bp|apte.ips_message place in APT entry
    tra end_cput_check
cput_wakeup:
    stz tmp_ring        set up for call to 'make_itt_message'
    stz dev_signal  count it as dev_signal
    aos dev_signal  count it as dev_signal
    lda =acput      ev message is 'cputimer'
    ldq =aimer
    staq    tmp_ev_message  save in stack
    ldaq    pds$timer_channel   copy ev channel into stack
    staq    tmp_ev_channel
    lda bp|apte.processid   copy processid for make_itt_message
    sta pds$arg_1
    stc1    pds$wakeup_flag not require unique message
    tsx6    LOCK_bp     cput_wakeup protects event thread
    tsx7    make_itt_message
    lda apte.wakeup_waiting,du Cheap wakeup of self
    orsa    bp|apte.flags
    tsx6    UNLOCK_bp       cput_wakeup is done
end_cput_check:

    lda bp|apte.flags   check stop or ips pending
    ana apte.stop_pending,du
    ora bp|apte.ips_message see if an ips signal must be sent
    tze no_more_pending no, skip over SMIC
    lda 1,dl        set ring alarm for exit from ring 0
    sta pds$alarm_ring
no_more_pending:

    lbar    pds$base_addr_reg   get base address reg for new process
    lra pds$alarm_ring  get his ralr setting too

    ldaq    prds$last_recorded_time calc this process's CPU time
    sbaq    bp|apte.time_used_clock fudge it for easy calc later
    staq    pds$cpu_time    save in the PDS for the process

    read_clock      " meter rest of getwork time
    sbaq    getwork_temp    set on entry to getwork by 'old_user'
    adaq    bb|getwork_time keep running total
    staq    bb|getwork_time
    aos bb|getwork_time+2   keep count of getworks
    eppbp   pds$apt_ptr,*   set bp to apt entry 
"
"   Still masked and wired, check for need to allocate a stack_0
"   and if needed do so.

    szn pds$stack_0_sdwp,*
    tnz already_got_stack_0 Doin' fine, no problem.

    tsx6    lock_stack_queue
    ldx0    ab|sdt.freep
    drlze   (pxss: no available stack_0) A fine time to run out of stacks.
    ldx4    ab|sdte.nextp,0 Thread out.
    stx4    ab|sdt.freep
    eax4    0
    stx4    ab|sdte.nextp,0 Clean thread
    eax2    bp|0
    sxl2    ab|sdte.aptep,0 For debugging.
    ldaq    ab|sdte.sdw,0
    staq    pds$stack_0_sdwp,*  Store the SDW in DSEG.
    cams    4       Clear cache. Think about it.
            "cams 0 not needed, did ldbr since last stack_0.
    tsx6    unlock_stack_queue
    lda apte.shared_stack_0,du  Flag stack to be returned
    orsa    bp|apte.flags
already_got_stack_0:

    lda bp|apte.flags   Load flags once again
    cana    apte.firstsw+apte.idle,du First time & ^idle ?
    tze stproc      is first time. do special return
"
    cana    apte.idle,du    idle process?
    tze no_idle_no_del  if not, don't do next check
    lxl1    prds$processor_tag  get CPU tag
    lda scs$processor_data,1 look at data for this CPU
    cana    processor_data.delete_cpu,du to be deleted?
    tnz delete_me       if so, stop it now
    szn temp1       before going idle, see if there was interference
    tze no_idle_no_del  was none, ok to idle
    lda 1,dl
    sta pds$alarm_ring  set ring_alarm to remind idle to come back
    lda apte.pre_empt_pending,du
    orsa    bp|apte.flags
    aos bb|gw_gp_window_count meter these
no_idle_no_del:

    lxl7    bp|apte.savex7  restore x7 for return
    tra 0,7

stproc: lda apte.firstsw,du turn ON flag saying we're initialized
    orsa    bp|apte.flags
    lda bb|process_initial_quantum  Give special quantum first time
    sta bp|apte.temax   put initial quantum in APTE
"
"Unlock,switch stacks, restore mask, and perform special first time call.
"
    eppsp   pds$last_sp,*
    ldaq    sb|stack_header.stack_begin_ptr Truncate PRDS.
    staq    sb|stack_header.stack_end_ptr
    epbpsb  sp|0
    lda bp|apte.processid
    cmpa    tc_data$initializer_id Is this the initializer?
    tze dont_reset_stack_0
    ldaq    sb|stack_header.stack_begin_ptr Clean out any old crud
    staq    sb|stack_header.stack_end_ptr
dont_reset_stack_0:
    inhibit on      <+><+><+><+><+><+><+><+><+><+><+><+>
    ldaq    scs$open_level
    lxl1    prds$processor_tag
    lprpab  scs$mask_ptr,1
    xec scs$set_mask,1
    inhibit off     <-><-><-><-><-><-><-><-><-><-><-><->
    eppap   =its(-1,1),*
    eppbp   pds$initial_procedure
    rtcd    bp|0




" 
"
"   NTO_CHECK -- This code checks for lost notifies.
"       Salient features include:
"       1. It is executed every nto_delta microsec.
"       2. It is entered with no locks set.
"       3. It looks for processes unchanged for > nto_delta.
"       4. If one is found it is notified, metered, and johndeaned.
"       5. If none, the time of the next check is set.
"       6. It returns to gw_retry with no locks set.
nto_check:
    tsx6    READ_LOCK
    eax2    bb|eligible_q_head
nto_loop:   ldx2    bb|apte.thread,2    step to next process
    szn bb|apte.sentinel,2
    tmi nto_reset_time  have done them all, all done
    tsx6    LOCK_x2     no need for speed here
    lxl0    bb|apte.state,2 only (ptl)waiters get nto'd
    cmpx0   waiting,du  waiter?
    tze nto_wait        this one is waiting now
    cmpx0   ptlocking,du    waiter on PTL?
    tnz nto_not     no
    lda =aptlw      yes, fake the event
    sta bb|apte.wait_event,2
    tra nto_wait        now go notify

nto_not:    tsx6    UNLOCK_x2       not waiting
    tra nto_loop        so go to next

nto_wait:   ldaq    bb|apte.state_change_time,2
    adl bb|nto_delta
    cmpaq   getwork_temp    State change more than nto_delta ago?
    tpl nto_not     No, go to next

    aos bb|nto_count    meter notify timeouts
    ldac    bb|apte.wait_event,2 Yes, reset state
    sta tmp_event
    sta bb|nto_event
    lda bb|apte.processid,2
    sta temp1
    lcx0    apte.page_wait_flag+1,du
    ansx0   bb|apte.flags,2
    ldx0    ready,du
    eppbp   bb|0,2
    tsx7    update_execution_state
    tsx6    UNLOCK_x2
    tsx7    get_processor
    szn bb|time_out_severity negative means dont print
    tmi nto_loop        can continue looping
    tsx6    UNLOCK      don't call out with lock set
"
"   call syserr (tc_data|time_out_severity,"pxss: notify time out: event=^w, pid=^w",
"        apte.wait_event, apte.processid)
"
    epplb   bb|time_out_severity
    sprilb  arg+2
    epplb   nto_message
    sprilb  arg+4
    epplb   tmp_event
    sprilb  arg+6
    epplb   temp1
    sprilb  arg+8
    epplb   fixed_desc
    sprilb  arg+10
    sprilb  arg+14
    sprilb  arg+16
    epplb   nto_message_desc
    sprilb  arg+12
    ldaq    =v18/8,18/4,18/8,18/0
    staq    arg
    call    syserr$syserr(arg)
    tra gw_retry        nto_check returns with no lock set
"
"Come here when scan of eligible queue has found no nto's.
nto_reset_time:
    read_clock  
    adl bb|nto_delta
    staq    bb|nto_check_time
    tsx6    UNLOCK      nto_check returns with no lock set
    tra gw_retry
nto_message_desc:
    oct 524000000047
nto_message:
    aci "pxss: notify time out: event=^w, pid=^w"

"
load_him:   cana    apte.being_loaded,du see if we must load him
    tnz gw_cant_run sombody already loading this process

    lda apte.being_loaded,du turn on being loaded flag
    orsa    bb|apte.flags,2
    tsx6    UNLOCK_x2
    tsx6    UNLOCK
    eppap   apt_ptr
    spriap  arg+2
    eppap   bb|0,2
    spriap  apt_ptr
    fld =1b24,dl
    staq    arg
    call    wired_plm$load(arg) load the process
    tsx6    LOCK_x2
    eppbp   bb|0,2      set bp to this entry
    lda bp|apte.wait_event   should we wait for the loading to complete ?
    tze loaded_test no, loading may be complete
    lda apte.page_wait_flag,du
    orsa    bp|apte.flags
    ldx0    waiting,du  -- Wait --
    tsx7    update_execution_state
    tra load_him_done   start over
" must verify loading with APTE locked
loaded_test:
    lda bp|apte.asteps  Get both asteps in Areg
    ldq ptw.wired+ptw.valid,dl  Get mask in Qreg
    anq sst$+aste_size,au   Require this page
    anq sst$+aste_size,al   AND that page
    cmpq    ptw.wired+ptw.valid,dl  have required bits on.
    tnz load_him_done   Didn't, must retry getwork.
    lda apte.loaded,du  turn loaded flag on
    orsa    bb|apte.flags,2
    aos bb|loadings
load_him_done:
    lcx0    apte.being_loaded+1,du Turn off being loading bit
    ansx0   bp|apte.flags
    tsx6    UNLOCK_x2
    tra gw_retry        load_him retries getwork from the top

find_idle_for_delete:
    tsx6    READ_LOCK
find_idle:
    ldx2    prds$idle_ptr+1
    tsx6    LOCK_x2
    stz bb|apte.te,2    zero idle's te
    tra gw_can_run
"
"   This section of code is invoked when a simulated alarm
"   clock interrupt is detected. Metering is kept of the lag of
"   the simulated timer from when it should really gone off.

alarm_timer:
    tsx6    WRITE_LOCK  alarm timer
at_retry:
    read_clock  
    sbaq    bb|next_alarm_time  compute lag in simulated timer
    tpnz    at_now
    tsx6    UNLOCK      false alarm
    tra gw_retry

at_now:
    aos bb|clock_simulations count number of clock simulations
    cmpq    bb|max_clock_lag    keep maximum lag
    tmi *+2     ..
    stq bb|max_clock_lag    ..
    adaq    bb|total_clock_lag  add to total
    staq    bb|total_clock_lag  ..
    read_clock      " remember clock time
    staq    bb|next_alarm_time  ..

"   The following code checks to see if a process timer should have
"   gone off.

next_timer:
    ldx2    bb|alarm_timer_list get next process on timer list
    tze priority_scheduling no one is on list
    eppbp   bb|0,2      set APT pointer
    ldaq    bp|apte.alarm_time  when should process timer go off
    ana =o777777,dl mask off thread
    cmpaq   bb|next_alarm_time  has time passed
    tpl priority_scheduling if not then we are done here
    ldx3    bp|apte.alarm_time  unthread from list
    stx3    bb|alarm_timer_list
    fld 0,dl        zero out time
    staq    bp|apte.alarm_time  ..
    lxl3    bp|apte.state   make sure process is alive
    cmpx3   stopped,du  ..
    tze next_timer  if process dead, forget it
    tsx6    LOCK_bp     wakup alrm
    ldaq    bp|apte.alarm_event get event channel
    tnz wakeup_alarm    if channel then go wakeup
    lda sys_info$alrm_mask
    orsa    bp|apte.ips_message place in APT entry
    tsx7    send_connect    send connect if running
    tra at_wake
wakeup_alarm:
    stz tmp_ring        send wakeup
    stz dev_signal  count it as dev_signal
    aos dev_signal  count it as dev_signal
    lda =aseta      event message is setalarm
    ldq =alarm      ..
    staq    tmp_ev_message  save in stack
    ldaq    bp|apte.alarm_event get event channel
    staq    tmp_ev_channel  save in stack
    lda bp|apte.processid   copy process id for ITT message
    sta pds$arg_1       ..
    stc1    pds$wakeup_flag not require unique message
    tsx7    make_itt_message    create wakeup message
at_wake:    tsx7    wake        wake up process
    tsx6    UNLOCK_bp       wakup alrm
    tra next_timer  see if more timers to go off

"   Now we check to see if there are any priority scheduling processes
"   that must have their priority boosted.

priority_scheduling:
    ldaq    bb|next_alarm_time  see if any processes need boosting
    cmpaq   bb|priority_sched_time ..
    tmi check_polling   no processes need boosting
    ldaq    bb|end_of_time_loc  set time to high value
    staq    bb|priority_sched_time
    ldx2    bb|min_wct_index    begin search of ready lists

ps_get_rq_head:
    ldx2    bb|wcte.thread,2    get index of top guy in ready queue
ps1:    eppbp   bb|0,2      get pointer to next entry in ready list
    szn bp|apte.sentinel    stop if end of ready list
    tmi ps_next_ready_queue Now at sentinel, x2-> wcte again
    ldx2    bp|apte.thread  save index to next before rethreading!
    lda bp|apte.flags2  is process in priority sched mode
    cana    apte.prior_sched,dl ..
    tze ps1     if not then go to next entry
    ldaq    bp|apte.state_change_time get time lost eligibility
    adl bb|priority_sched_inc add in increment for rescheduling
    cmpaq   bb|next_alarm_time  ..
    tmi ps_boost        ..
    cmpaq   bb|priority_sched_time see if this is next process to be boosted
    tpl ps1     if not then go to next entry
    staq    bb|priority_sched_time if so then remember time
    tra ps1     go to next entry
ps_boost:
    szn bp|apte.ti  already interactive?
    tze ps1     don't boost again, and avoid tail chasing
    tsx0    setup_p_int boost priority
    tsx7    unthread        thread out of ready list
    tsx7    sort_in     sort into ready list in new place
    aos bb|boost_priority   count number of boosts
    tra ps1     go to next entry
ps_next_ready_queue:
    eax2    size_of_wct_entry,2 Move to next wcte
    cmpx2   bb|max_wct_index    If If there is one.
    tmoz    ps_get_rq_head
"
"   Now we check to see if the disk DIM or tty DIM need
"   to be polled.

check_polling:
    eax4    n_polling_table-4   initialize index for table search
polling_loop:
    xec polling_table+2,4   execute before test
test_poll:
    ldaq    bb|next_alarm_time  get time for this alarm
    ldx3    polling_table,4 get address of link to time
    cmpaq   lp|0,3*     has time matured?
    tmi skip_polling    if not, skip this call

    adl polling_table+1,4   compute time of next poll
    staq    lp|0,3*     and set new time

    tsx6    UNLOCK      call out with APT unlocked
    lxl3    polling_table,4 get address of routine to call
    call    lp|0,3*(null_arglist) make call
    tsx6    WRITE_LOCK  relock the APT now
    tra next_poll       on to the next

skip_polling:
    xec polling_table+3,4   execute after test
next_poll:
    eax4    -4,4        step to next table entry
    tpl polling_loop    and loop

"   Now we compute the next simulated alarm clock time.

compute_next_alarm:
    ldx2    bb|alarm_timer_list get next process timer
    tze *+5     no process timer
    ldaq    bb|apte.alarm_time,2 ..
    ana =o777777,dl mask off thread
    cmpaq   bb|priority_sched_time is it before priority sched time
    tmi *+2
    ldaq    bb|priority_sched_time if sched time first use it

    eax4    n_polling_table-4   initialize index for table search
next_alarm_loop:
    ldx3    polling_table,4 get pointer to time
    cmpaq   lp|0,3*     test for earliest time
    tmi *+2     ..
    ldaq    lp|0,3*     ..

    eax4    -4,4        try next table entry
    tpl next_alarm_loop ..

    staq    bb|next_alarm_time  set time for next alarm
    tra at_retry        go check time again

    even
null_arglist:
    oct 4,0     arglist with no args

polling_table:

    link    disk_polling_time,tc_data$disk_polling_time
    link    disk_poll,page$time_out
    zero    disk_polling_time,disk_poll
    dec 15b15
    nop
    nop

    link    iobm_polling_time,tc_data$iobm_polling_time
    link    iobm_poll,iobm$time_out
    zero    iobm_polling_time,iobm_poll
    dec 0b15
    nop
    nop

    link    ioi_polling_time,tc_data$tape_polling_time
    link    ioi_poll,ioi_timer$ioi_timer
    zero    ioi_polling_time,ioi_poll
    dec 16b15
    nop
    nop

    link    mos_polling_time,tc_data$mos_polling_time
    link    mos_poll,mos_memory_check$poll
    zero    mos_polling_time,mos_poll
    dec 600b15
    nop
    nop

    link    volmap_poll_time,tc_data$volmap_polling_time
    link    volmap_poll,page$poll_volmap_io
    zero    volmap_poll_time,volmap_poll
    dec 30b15
    nop
    nop

    equ n_polling_table,*-polling_table
" 
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "" " "
"   
"   COMPUTE_VIRTUAL_CLOCKS -- procedure to figure out what type of
"   idle time we have, and also to update the time used in the
"   APT entry for the process just run.
"   A meter lock protects per-system doubleword variables
"
"   Idle time is categorized as follows:
"
"   zero idle       - all processes blocked except idle processes
"   nmp idle        - all processes which could be eligible are eligible
"   loading idle    - not all processes which could be eligible are
"               eligible, and not all eligible processes are loaded
"   work class idle - not all processes which could be eligible are
"               eligible, at least one work class was
"               skipped because of work class limits
"               (governing, max eligible for work class),
"               and system maxe has not been reached
"   mp idle         - not all processes which could be eligible are
"               eligible, and criteria for work class idle
"               not met
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

compute_virtual_clocks:
    eppbp   pds$apt_ptr,*   set bp to this process
    lda pds$page_waits  copy page fault count into APT entry
    sta bp|apte.page_faults
    ldaq    bp|apte.virtual_cpu_time Remember bp's vcpu
    staq    temp        Use temp for delta virtual time.

    read_clock
    staq    bb|last_time    save last time anyone run
    sbaq    prds$last_recorded_time delta t in microseconds
    szn prds$last_recorded_time+1 if zero, first time
    tnz *+3     set delta t to 0 if first
    staq    prds$last_recorded_time
    tra cvidt
    staq    delta_t
    asq bb|governing_credit_bank
    
    adaq    bp|apte.time_used_clock update time used in APT
    staq    bp|apte.time_used_clock
    sbaq    pds$virtual_delta   update the virtual CPU time
    staq    bp|apte.virtual_cpu_time

    sbaq    temp        Subtract old vcpu from new
    staq    temp        Remember for idle meters.

    ldaq    delta_t     meter last recorded time
    adaq    prds$last_recorded_time
    staq    prds$last_recorded_time

mlock_loop:
    ldac    bb|metering_lock    LOCK LOCK LOCK LOCK LOCK LOCK LOCK
    tnz mlocked     OK- meters are locked
    tra mlock_loop  retry locking meters
mlocked:
    ldaq    delta_t     meter total CPU time for system
    adaq    bb|processor_time
    staq    bb|processor_time

    ldaq    prds$last_recorded_time compute ave queue length
    anq =o3777777       sample every sec (2**20 usec)
    adq delta_t+1
    qrl 20-18       convert to sec
agl:    eaq -1,qu       count seconds
    tmi age
    lda bb|statistics+running get queue length
    ada bb|statistics+ready
    ada bb|statistics+waiting running+ready+waiting
    als 18      give answer 6 octal points
    sba bb|avequeue new ave=oldave+(cur-oldave)/64
    ars 6
    asa bb|avequeue
    lda bb|n_eligible   now get average eligible
    als 18
    sba bb|ave_eligible
    ars 6
    asa bb|ave_eligible
    tra agl

age:

    lda bp|apte.flags   check for idle process
    cana    apte.idle,du
    tnz cvidle      is

    ldx0    prds$depth  count runs at depth
    aos bb|depths,0
    adx0    prds$depth  double index
    ldaq    delta_t     bump time at level
    adaq    bb|tdepth,0
    staq    bb|tdepth,0

    ldaq    temp        Update vcpu used by nonidle
    adaq    bb|system_virtual_time
    staq    bb|system_virtual_time

    ldx0    bp|apte.wct_index   Update work_class data.
    ldaq    delta_t     Workclasses get % of tcpu - not vcpu
    szn bb|wcte.realtime,0  Realtime not add to bank.
    tnz *+2
    asq bb|credit_bank  But others do.

    adaq    bb|wcte.cpu_sum,0   Add to time gotten
    staq    bb|wcte.cpu_sum,0
    lcq delta_t+1       Decrement credits.
    asq bb|wcte.credits,0
    lda bb|wcte.governed_word,0     Is W.C. governed
    cana    wcte.governed,du
    tze m_unlock        no
    asq bb|wcte.governing_credits,0
    tra m_unlock        Now go unlock metering data.

cvidle: ldaq    temp        up total idle vcpu
    adaq    bb|idle_time
    staq    bb|idle_time

    ldaq    delta_t     Update idle  real cpu time.
    adaq    bb|gross_idle_time
    staq    bb|gross_idle_time

    ldaq    temp        up idle vcpu by type
    ldx0    bp|apte.term_processid recall our idle type
    tze m_unlock        ignore first time
    adaq    bb|0,0
    staq    bb|0,0

m_unlock:   stc1    bb|metering_lock    UNLOCK UNLOCK UNLOCK UNLOCK UNLOCK
cvidt:  lda bb|apte.flags,2 Is new guy idle?
    cana    apte.idle,du
    tze 0,7     no, not idle
    eax0    zero_idle       yes, figure type
    lda bb|statistics+running
    ada bb|statistics+ready
    ada bb|statistics+waiting
    ada bb|statistics+ptlocking
    tze cvst        Will be zero_idle
    eax0    nmp_idle
    cmpa    bb|n_eligible
    tze cvst        Will be NMP idle
    eax0    loading_idle
    lxl1    bb|eligible_q_tail
    ldx1    bb|apte.flags,1
    canx1   apte.loaded,du
    tze cvst        Will be Loading idle
    eax0    mp_idle
    szn temp2       Work class limit reached?
    tze cvst        No, will be MP idle
    lxl1    bb|n_eligible   Are we at system max eligible?
    cmpx1   bb|max_eligible
    tpl cvst        Yes, will be MP idle
    eax0    work_class_idle No, will be work class idle
cvst:   stx0    bb|apte.term_processid,2 Remember idle type.
    tra 0,7
" 
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   SET_NEWT  -- procedure to figure out how long the new process
"   should run.
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

set_newt:
    lda bb|apte.flags,2 if idle, give him much time
    cana    apte.idle,du
    tze sn1     if not idle then compute new timer

    read_clock      " compute time to next alarm
    sbaq    bb|next_alarm_time  ..
    tpl setsmall        make sure time has not already passed
    negl    0       make time positive
    cmpaq   idle_runout is it very large
    tmi sn2     no, go set it
    ldaq    idle_runout yes, reduce it
    tra sn2     then set it
sn1:

    eaa 0
    ldq bb|apte.temax,2 Pick up correct quantum
    sbq bb|apte.te,2
    cmpq    4000,dl
    tpl sn_ck_big
    ldq 4000,dl
    tra sn2
sn_ck_big:
    cmpaq   bb|max_timer_register
    tmi sn2
    ldaq    bb|max_timer_register

sn2:
    qls 3+1     binary point at 3, round
    div apte.timer_factor,dl convert to clock ticks
    adq 1,dl        round
    qls 12-1        timer_shift, round bit
    tpl *+2     must have positive time value
setsmall:   ldq =o010000,dl small timer value
    stq prds$last_timer_setting
    tra 0,7     success return

    even
idle_runout:
    dec 0,250000        timer runout for idle process
" 
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   hash_LOCK -- called with alleged processid in pds$arg_1
"
"       if processid is valid:
"           apt_ptr, BP -> APTE
"           APTE is locked
"           return to 1,7
"
"       if processid is invalid:
"           apt_ptr, BP = null
"           nothing new is locked
"           return is indirect through 0,7
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

hash_LOCK:
    ldq pds$arg_1       get processid
    tmi not_found       Neg pid may cause overflow fault
    sbq bb|apt_offset   subtract apt offset
    div size_of_apt_entry,du
    cmpq    bb|apt_size check against bounds of array
    trc not_found       was invalid offset

    mpy size_of_apt_entry,dl Apply array to index
    eppbp   tc_data$apt,ql
    spribp  apt_ptr     return pointer to apt entry
    tsx6    LOCK_bp     hash_lock must lock to check pid
    ldq pds$arg_1
    cmpq    bp|apte.processid   make sure it's the same one
    tze 1,7     success return from hash_lock
    tsx6    UNLOCK_bp       hash_lock found wrong pid
not_found:
    eppbp   null,*      get null pointer, return
    spribp  apt_ptr     null this too
    tra 0,7*        hash_lock failure indirect return

    even
" 
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   IPS_WAKEUP -- entry to wake up a process, turn on the ips_pending
"   flag and resort the process with high priority.  
"   Call is
"       call pxss$ips_wakeup(processid,message)
"   wher processid is the process id of the process to be awakened, and
"   message specifies which IPS message is being sent.
"   Currently 'message' is declared char (4), eventually it will be fixed
"
" " " " " " " " " " " " " " " " " " " " " " "" " " "  " " " " " " " " " " " " "


ips_wakeup_int:
    tsx6    setup_
    stz pds$wakeup_flag save info about which entry
    tra ijoin
ips_wakeup:
    tsx6    setup_
    stc1    pds$wakeup_flag
ijoin:
    lda ap|4,*      pick up char string
    sta pds$arg_2       save in PDS
    lda ap|2,*      get processid
    sta pds$arg_1
    tsplb   setup_check switch stacks and lock
    arg 0,6
    tsx6    WRITE_LOCK  ips_wakeup protect pid, rethreads
    tsx7    hash_LOCK       hash search for the APTE
    arg ips_wakeup_returns_nul no, don't continue
    lxl0    bp|apte.state   make sure not stopped
    cmpx0   stopped,du
    tze ips_wakeup_returns
    eppap   sys_info$ips_mask_data get pointer to ips info
    ldq ap|ips.count    get count of number of ips signals
    mpy ips.size,dl get index to last one
    lda pds$arg_2       pick up char string
    cmpa    ap|ips.data-ips.size,ql get first word of char string
    tze *+5     if the same, found it
    sbq ips.size,dl go back to next entry
    tze ips_wakeup_returns
    tpl *-4     loop back if more
    tra ips_wakeup_returns
    lda ap|0,ql     get mask bit
    orsa    bp|apte.ips_message turn on flag in APT entry

    szn pds$wakeup_flag should we give good priority ?
    tpnz    ij2     no, skip code which gives priority

    lxl0    bp|apte.state   get state of process being awakened
    cmpx0   ptlocking,du    bugfix:dont OOB quit buckets
    tpl *+2     bugfix:dont OOB quit buckets
    aos bb|quit_counts,0    and count this ips priority wakeup

    ldq bp|apte.te  first update ti
    adq bp|apte.ts
    adq bp|apte.ti
    mpy bb|quit_priority    get new priority
    lls 36-18       (binary point at 18)
    eax3    0,au        Save ti in X3
    tsx0    setup_p_int boost priority
    stx3    bp|apte.ti  Store ti from X3.
ij2:
    ldx0    bp|apte.flags   don't move if eligible
    canx0   apte.eligible,du
    tnz ips_wakeup_returns
    lxl0    bp|apte.state
    cmpx0   blocked,du  if not blocked, move in q
    tze *+3
    tsx7    unthread
    tsx7    sort_in

    tsx7    send_connect    send connect if process running
    tsx7    wake        wake up process
ips_wakeup_returns:
    tsx6    UNLOCK_bp
ips_wakeup_returns_nul:
    tsx6    UNLOCK
    tra switch_back Return to caller

"
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
"
"   SETUP_INT: called via tsx0, sets apte variables to give
"   high priority and initial quantum following interaction.
"   SETUP_P_INT: gives high priority etc w/o interaction.
"

"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
setup_p_int:
    aos bb|p_interactions   Meter priority interactions.
    lcx1    apte.interaction+1,du
    ansx1   bp|apte.flags   Turn off interaction flag.

setup_int:
    ldx1    bp|apte.wct_index
    stz bp|apte.ti  High priority.
    stz bp|apte.ts
    stz bp|apte.te
    read_clock      " Calc new deadline.
    adaq    bb|wcte.resp1,1 By adding to curr time.
    staq    bp|apte.deadline

    ldq bb|wcte.quantum1,1  Pick up new quantum.
    lda bb|deadline_mode    If deadline mode
    ada bb|wcte.realtime,1  or realtime process
    tpnz    *+2     use per-workclass quantum.
    ldq bb|tefirst  use default.
    stq bp|apte.temax

    tra 0,0     Return from setup_int.
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   SETUP_IO_REALTIME: called via tsx0, sets apte variables
"   for a realtime burst of eligibility
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

setup_io_realtime:
    read_clock
    adl bb|realtime_io_deadline
    staq    bp|apte.deadline
    ldq bb|realtime_io_quantum  " And quantum
    stq bp|apte.temax
    aos bb|realtime_priorities  " Meter
    tra 0,0

"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   REVOKE_ELIG: called with write_lock, turns off elig, decrements counters
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

revoke_elig:
    lcx1    apte.eligible+1,du  take away eligibilty
    ansx1   bp|apte.flags
    lca 1,dl        Put -1 in Areg for subtracts ..
    asa bb|n_eligible
    lxl1    bp|apte.flags2  If was batch
    canx1   apte.batch,du   then decrement batch counter
    tze *+2     else dont
    asa bb|num_batch_elig   use -1 in the A

    ldx1    bp|apte.wct_index   Decrement workclass counter.
    asa bb|wcte.nel,1
    lda bp|apte.saved_temax Make up for init decrement
    asa bb|wcte.credits,1   of wc credits.
    ldq bb|wcte.governed_word,1 Is wc governed
    canq    wcte.governed,du
    tze *+2     No
    asa bb|wcte.governing_credits,1
    lca bp|apte.ws_size Sub ws from sum of elig ws
    asa bb|ws_sum
    stz bp|apte.wait_event  he won't get notified

"   Give up the stack_0 if it has a shared one

    lda bp|apte.flags
    cana    apte.shared_stack_0,du
    tze 0,7

"   Don't give up the stack for a stopped process, unless the
"   limit of such suspended stacks has been reached

    lxl0    bp|apte.state
    cmpx0   stopped,du  is he stopped
    tnz give_up_stack   no--OK to release stack
    lda bb|stopped_stack_0  check limit on suspended
    cmpa    bb|max_stopped_stack_0    stacks
    tpl give_up_stack   too bad--flush the stack
    aos bb|stopped_stack_0  add to count of suspended stacks
    ldx0    -1,du       and decrement count of
    asx0    bb|max_max_eligible   available stacks
    tra 0,7
give_up_stack:
    
"   Check the validity of the stack

    eppab   sst$
    ldaq    pds$stack_0_sdwp,*
    arl 12
    sbla    ab|sst.ptwbase
    eppab   ab|0,al     ptr to ptw for page 0

    lda ab|0        get the ptw
    cana    ptw.wired,dl    check page 0 wired
    drlnz   (pxss: stack_0 page 0 wired) SHOULD NOT BE!!!!

    tsx6    lock_stack_queue
    ldaq    pds$stack_0_sdwp,*  Get stack SDW
    drlze   (pxss: no stack_0 sdw) LOSE! snb/0
    lxl0    ab|sdt.num_stacks   This is how many slots
    eax4    0       Entry index
free_stack_0_loop:
    cmpaq   ab|sdt.sdw,4
    tze free_stack_0_got
    eax4    sdte_size,4
    eax0    -1,0
    tpnz    free_stack_0_loop
    drltra  (pxss: freeing unknown stack_0) STACK NOT FOUND
free_stack_0_got:
    tsx6    free_stack_0    Give it up
    fld 0,dl
    staq    pds$stack_0_sdwp,*  Clear out SDW for getwork check.

    tsx6    unlock_stack_queue

    lcx0    apte.shared_stack_0+1,du
    ansx0   bp|apte.flags   reset flag
    tra 0,7     return from revoke_elig

"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   RESCHEDULE: called with write_lock set
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "


reschedule:
    lda bp|apte.te  update te into ts
    tmi *+2     avoid neg ts
    asa bp|apte.ts

    ldx1    bp|apte.wct_index
    read_clock      " Calc new deadline.
    adaq    bb|wcte.resp2,1 By adding to cutime.
    staq    bp|apte.deadline

    ldq bb|wcte.quantum2,1  Pick up new quantum.
    lda bb|deadline_mode    If in deadline_mode
    ada bb|wcte.realtime,1  or realtime process
    tpnz    *+2     then use per workclass quantum.
    ldq bb|telast
    stq bp|apte.temax

    lda bb|tefirst  if ts>min(tefirst+ti,timax)
    ada bp|apte.ti
    cmpa    bp|apte.timax
    tmi *+2
    lda bp|apte.timax
    cmpa    bp|apte.ts
    tpl nupti
    lda bp|apte.ts  update ts into ti
    asa bp|apte.ti
    stz bp|apte.ts
nupti:  stz bp|apte.te

    lda bp|apte.flags2  is this a guaranteed eligibility process
    cana    apte.prior_sched,dl ..
    tze rs_ps_done  if not then OK
    aos bb|lost_priority_eligibility record loss of eligibility
    ldaq    bp|apte.state_change_time compute when to boost priority
    adl bb|priority_sched_inc
    cmpaq   bb|priority_sched_time remember when to boost next
    tpl rs_ps_done  ..
    staq    bb|priority_sched_time
    cmpaq   bb|next_alarm_time  update time of next alarm if necessary
    tpl rs_ps_done  ..
    staq    bb|next_alarm_time
rs_ps_done:
    tra 0,7     reschedule returns

"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   purge_UNLOCK: called with write_lock and apte_lock, it unlocks both
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "


purge_UNLOCK:
    stz bp|apte.ws_size

    tsx6    UNLOCK_bp       UNLOCK own apte before call out
    tsx6    UNLOCK

    szn bb|post_purge_switch If at all.
    tze pU_ret
    ldx1    bp|apte.wct_index
    szn bb|wcte.purging,1   Purge on per wc basis
    tze pU_ret
    call    page$post_purge clean up last process's pages
pU_ret: tra 0,7     Return from purge_UNLOCK


" 
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   stop_check_ -- call if old user probably is stopped
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "


stop_check_:
    tsx6    WRITE_LOCK  prevent empty_t interference
    lxl0    bp|apte.state   check for really stopped
    cmpx0   stopped,du
    tnz stop_ul     not stopped, dont notify
    lda =astop      message will be "stopstop"
    sta tmp_ev_message  ..
    sta tmp_ev_message+1
    tsplb   wake_term_pid   send stopstop to term pid
    eppbp   apt_ptr,*       repair BP for next test
stop_ul:    tsx6    UNLOCK
    tra end_stop_check  stop_check_ ret to getwork



" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   cpu_monitor_ -- called if probably need to send cpulimit wakeup
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

cpu_monitor_:
    ldaq    bp|apte.virtual_cpu_time
    lrs 10      convert to 1/1024 secs
    cmpq    bp|apte.cpu_monitor
    tmi end_cpu_monitor
    tsx6    WRITE_LOCK
    tsx6    LOCK_bp
    szn bp|apte.cpu_monitor
    tze cm_not
    ldaq    bp|apte.virtual_cpu_time
    lrs 10      convert to 1/1024 secs
    cmpq    bp|apte.cpu_monitor
    tmi cm_not
    stz bp|apte.cpu_monitor clear cell
    tsx6    UNLOCK_bp
    ldaq    cpulimit_msg
    staq    tmp_ev_message
    tsplb   wake_term_pid   send cpulimit to term_pid
cm_done:    tsx6    UNLOCK
    tra end_cpu_monitor return to gw

cm_not: tsx6    UNLOCK_bp
    tra cm_done

    even
cpulimit_msg:
    aci "cpulimit"
"

" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   WAKE_TERM_PID -- send tmp_ev_message to term_pid over term_chan
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

wake_term_pid:
    stz dev_signal  count it as dev_signal
    aos dev_signal  count it as dev_signal
    ldaq    bp|apte.term_channel
    staq    tmp_ev_channel
    stz tmp_ring
    lda bp|apte.term_processid
    sta pds$arg_1
    stc1    pds$wakeup_flag not require unique message
    eppbp   bb|0,au     make bp -> target_apte
    tsx6    LOCK_bp     wake_term_pid locks target of wakeup
    tsx7    make_itt_message
    tsx7    wake
    tsx6    UNLOCK_bp       wake_term_pid unlocks target apte
    tra lb|0        wake_term_pid returns

"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   DELETE_ME
"
"   This is the legendary CPU Graveyard, where CPUs go to die
"   when taken off-line.  When a CPU is to be deleted, getwork
"   will run only an idle process on it.  When this happens,
"   the process will transfer to this routine.  This routine
"   does the following:
"
"   1. Under the Global APT lock, checks whether the system default
"      CPU set would contain no online CPUs. 
"
"   2. If the system default CPU set would contain no online
"      CPUs, it changes the system default CPU set to all
"      CPUs, unlocks, and prints a message on the console.
"      Under the Global APT lock, checks the system default CPU
"      set again.  This second check is necessary to avoid races,
"      since the call to syserr must be made with the Global APT
"      lock unlocked. If the second check fails, the first is
"      repeated.  This should happen very rarely.
"
"   3. Changes the state of the idle process for this CPU to
"      empty, unthreading it, but not returning it to the empty
"      list.
"
"   4. Under the connect lock, turn off the bit for this CPU
"      in scs$processor (this must be done under the connect
"      lock to prevent other CPUs doing connects from spinning
"      forever waiting for this one to respond).
"
"   5. Walk the APTE array, looking for processes whose set of
"      required CPUs includes no online CPUs. Any such are changed
"      to require the current system default. This must be done
"      under the Global APT lock.
"
"   6. Unlock the Global APT lock and die (inhibited DIS).
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

delete_me:
    tsx6    WRITE_LOCK  delete_me locks out sprq

    lda prds$processor_pattern  bit for this CPU
    era scs$processor       Areg = bits for remaining CPUs
    ana bb|default_procs_required   Any left in default?
    tnz procs_required_ok       Yes
retry_delete_me:                " Also come here if lost race
    lda all_procs_required,du   Set default to all CPUs
    sta bb|default_procs_required

    tsx6    UNLOCK      Unlock before call-out

"   call    syserr (3, "pxss: System default procs required reset to ABCDEFGH")

    epplb   reset_system_procs_severity
    sprilb  arg+2
    epplb   reset_system_procs_mess
    sprilb  arg+4
    epplb   fixed_desc
    sprilb  arg+6
    epplb   reset_system_procs_desc
    sprilb  arg+8
    ldaq    =v18/4,18/4,18/4,18/0
    staq    arg
    call    syserr$syserr(arg)

    tsx6    WRITE_LOCK  relock before checking again

    lda prds$processor_pattern  Bit for this CPU
    era scs$processor       Areg = bits for remaining CPUs
    ana bb|default_procs_required   Any left online
    tze retry_delete_me     No - lost race

procs_required_ok:

"   Walk APTE array, looking for processes which can't run

    ldq scs$processor   Bits for CPUs still running
    anq apte.procs_required_mask,du Strip out garbage
    erq prds$processor_pattern  And bit for this CPU
    lca 1,dl
    era apte.procs_required_mask,du To reset procs required
    ldx3    bb|default_procs_required   To set to default
    anx3    apte.procs_required_mask,du Strip out garbage
    ldx2    apte.default_procs_required,du To set bit saying it's default
    eppap   tc_data$apt Begin of APTE array
    lxl1    bb|apt_size Number APTEs
check_proc_loop:
    ldx0    ap|apte.flags
    canx0   apte.idle,du    Skip idle processes
    tnz check_proc_next
    canq    ap|apte.procs_required Any left for this process?
    tnz check_proc_next Yes
    ansa    ap|apte.procs_required Clear procs required
    orsx3   ap|apte.procs_required Set to default
    orsx2   ap|apte.flags   And remember it's the default
check_proc_next:
    eppap   ap|size_of_apt_entry Next APTE
    eax1    -1,1        One less to go
    tpnz    check_proc_loop But still some left

    inhibit on      <+><+><+><+><+><+><+><+><+><+><+><+>

    lxl1    prds$processor_tag
    lcx0    processor_data.online+1,du turn this bit OFF
    ansx0   scs$processor_data,1 ..

    eppbp   prds$idle_ptr,* bp -> APTE for idle process
    tsx6    LOCK_bp
    ldx0    empty,du        set empty state
    tsx7    update_execution_state ..
    tsx6    UNLOCK_bp

    inhibit off     <-><-><-><-><-><-><-><-><-><-><-><->
    lda pds$processid   lock the connect lock
    stac    scs$connect_lock    before diddling scs$processor
    nop
    nop
    tnz -3,ic
    lca 1,dl        one's in A
    era prds$processor_pattern make a mask
    ansa    scs$processor   turn off bit for this processor
    lda 0,dl        clear the A
    ansa    scs$connect_lock    can undo lock now

    inhibit on      <+><+><+><+><+><+><+><+><+><+><+><+>
    lxl1    prds$processor_tag
    lcx0    processor_data.delete_cpu+1,du turn this bit OFF
    ansx0   scs$processor_data,1
    ldx0    processor_data.offline+processor_data.halted_cpu,du turn these bits ON
    orsx0   scs$processor_data,1

    tsx6    UNLOCK      undo the lock before dying

    dis =o777,dl
    tra *-1

    inhibit off     <-><-><-><-><-><-><-><-><-><-><->

reset_system_procs_severity:
    dec 3       " severity of syserr message
reset_system_procs_mess:
    aci "pxss: System default procs required reset to ABCDEFGH"
    equ reset_system_procs_words,*-reset_system_procs_mess
    equ reset_system_procs_chars,4*reset_system_procs_words
reset_system_procs_desc:
    vfd 1/1,6/21,5/0,24/reset_system_procs_chars
"
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " 
"
"   Hardcore stack queue locking/unlocking
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

lock_stack_queue:
    lda pds$processid
    eppab   stack_0_data$
    cmpa    ab|sdt.lock
    drlze   (pxss: mylock on stack queue) mylock err
    stac    ab|sdt.lock
    nop
    nop
    tnz *-3
    nop
    cmpa    ab|sdt.lock Check for stac loss.
    drlnz   (pxss: lock_stack_queue stac failed)
    tra 0,6     That's why locks are expensive in Multics.

unlock_stack_queue:
    ldq pds$processid
    cmpq    ab|sdt.lock Check for other random lossage
    drlnz   (pxss: unlock_stack_queue not my lock)
    eaa 0
    stacq   ab|sdt.lock
    drlnz   (pxss: unlock_stack_queue stacq failed) stacq claims failure
    nop
    cmpq    ab|sdt.lock Stacq really win?
    drlze   (pxss: unlock_stack_queue stacq failed)
    tra 0,6
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   subroutine to return a stack_0 to the free list
"
"   tsx6    free_stack_0
"
"   On entry -
"        sdt lock must be owned
"        ab -> stack_0_data$
"        x4 = index of sdte for this stack
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
    
free_stack_0:
    eax0    0
    sxl0    ab|sdt.aptep,4  No aptep
    eax0    ab|sdt.stacks,4 Thread in
    ldx4    ab|sdt.freep
    stx4    ab|sdte.nextp,0
    stx0    ab|sdt.freep
    tra 0,6
    
        
"
" BEGIN MESSAGE DOCUMENTATION

" Message:
" pxss: notify time out: event=ZZZZZZZZZZZZ, pid=XXXXXXXXXXXX

" S:    $info

" T:    $run

" M:    A hardcore event has not occurred within a reasonable time.
" This may be due to hardware problems
" or to a programming error.
" The system attempts to continue operation.

" A:    If this message persists, contact system programmers.


" Message:
" pxss: notify time out:  event=ZZZZZZZZZZZZ. During init/shutdown.

" S:    $info

" T:    $init

" M:    A hardcore event has not occurred within a reasonable time
" during system initialization or shutdown.  This may be due to hardware 
" problems or to a programming error.  The system attempts to continue
" initialization or shutdown.

" A:    If this message persists, contact system programmers.


" Message:
" pxss: System default procs required reset to ABCDEFGH

" S:    $beep

" T:    During CPU deconfiguration.

" M:    Due to the deletion of a CPU, there were no online CPUs
" remaining in the default set of CPUs.  These CPUs are the only CPUs
" on which processes can run which have not requested to be run on
" specific CPUs. The default set of CPUs has been changed to all
" online CPUs.

" A:    Notify the site administrator.

" Message:
" pxss: APTE not locked
"
" S:    $crash
"
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: APTE disdains all processors
"
" S:    $crash
"
" T:    $run
"
" M:    $err
"   There are no processors-required set for this APTE.
"
" A:    $recov

" Message:
" pxss: No term_processid
"
" S:    $crash
"
" T:    $run
"
" M:    $err
"   As a process was about to indicate its demise to the master process,
"   it discovered, to its chagrin, that it had forgotten who that was.
"
" A:    $recov

" Message:
" pxss: Returned from getwork
"
" S:    $crash
"
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: sprq already on prds
"
" S:    $crash
"
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: empty_t APTE not stopped or empty
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"   an attempt was made to clear an APTE that of a process that was neither
"   stopped nor empty.
"
" A:    $recov

" Message:
" pxss: APT not locked
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: unthread null back ptr
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: unthread prev.fp ^= cur
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: unthread null cur.fp
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: unlock apt read lock bad count
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: write_to_read bad lock count
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: write_to_read ldac failed
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"   This indicates a hardware error.
"
" A:    $recov

" Message:
" pxss: UNLOCK_bp not locked
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: UNLOCK_X2 not locked
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: UNLOCK_x3 not locked
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: subroutine_save stack overflow
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: ITT overflows
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: untenable empty APTE
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: untenable blocked APTE
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: untenable stopped APTE
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: thread_him_in already threaded
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: thread_him_in x1 zero
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: thread_him_in x4 zero
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: thread_him_in x4->apte.fp ^= x1
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: apte.state ^= ready
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: thread_him_in x0 zero
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: no available stack_0
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: stack_0 page 0 wired
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"   process loading was about to wire the first page of the ring 0 stack when
"   it discovered that it had been beaten to the punch.
"
" A:    $recov

" Message:
" pxss: no stack_0 sdw
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: freeing unknown stack_0
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"   the stack_0 being returned could not be found in the list of stack_0s.
"
" A:    $recov

" Message:
" pxss: mylock on stack queue
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: lock_stack_queue stac failed
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"   This indicates a hardware error.
"
" A:    $recov

" Message:
" pxss: unlock_stack_queue not my lock
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"
" A:    $recov

" Message:
" pxss: unlock_stack_queue stacq failed
"
" S:    $crash
" 
" T:    $run
"
" M:    $err
"   This indicates a hardware error.
"
" A:    $recov

" END MESSAGE DOCUMENTATION

    end




            meter_response_time.alm         05/15/82  1513.2rew 05/15/82  1451.8       95598



"  ******************************************************
"  *                                                    *
"  *                                                    *
"  * Copyright (c) 1972 by Massachusetts Institute of   *
"  * Technology and Honeywell Information Systems, Inc. *
"  *                                                    *
"  *                                                    *
"  ******************************************************

    name    meter_response_time

" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   subroutine to monitor response times of interactive processes
"
"   The state of a process is defined by a finite-state automaton
"   representing that process.  This finite-state automaton is
"   viewed as follows:
"
"
"                                +------------------+    
"                                |                  |
"                            |  Processing      |
"                        |              |
"                        +------------------+
"                  call ring-0 tty   |          ^
"          for next interaction      |          |  return from ring-0 tty with 
"                                    V          |     interaction
"         award eligibility      +------------------+
"              +-------------->  |                  |
"              |                 |     Other        |
"     +------------------+       |                  |   
"     |                  |       +------------------+
"     |     Queued       |         |        ^   
"     |                  |         |block   |
"     +------------------+         |        | non-tty wakeup
"              ^                   V        |
"              |          +------------------+
"              |          |                  |
"              +----------|     Blocked      |
"          tty wakeup     |                  |
"                 +------------------+
"     
"   This subroutine implements the finite-state automaton described
"   for each process.  Transitions between states are defined by calls
"   to the subroutine.  There are two calling sequences, as follows:
"
"   External to bound_traffic_control_priv:
"
"        call meter_response_time (processid, transition)
"
"
"   Internal to bound_traffic_control_priv:
"
"       tsx7    meter_response_time$tc
"
"            pr2 -> apte for process
"            pr3 -> base of tc_data$
"            qreg = transition number
"
"   Written April 1981 by John J. Bongiovanni
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"


" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   Table to drive finite-state automaton
"
"   transition_table implements the following finite-state
"   automaton (ref. MTB-489):
"
"          
"                                   Transitions
"          
"          State                1   2   3   4   5   6   
"                             +---+---+---+---+---+---+
"          Initial (I)        | I | I | P | I | I | Q |
"                             +---+---+---+---+---+---+
"          Blocked (B)        | I | I | I | I | O | Q |
"                             +---+---+---+---+---+---+
"          Queued (Q)         | O | I | I | I | Q | I |
"                             +---+---+---+---+---+---+
"          Other (O)          | O | O | P | B | O | O |
"                             +---+---+---+---+---+---+
"          Processing (P)     | P | O | I | B | P | I |
"                             +---+---+---+---+---+---+
"          
"          
"          
"                    Transitions:
"          
"              1 - Award eligibility
"          
"              2 - Call ring-0 tty for next interaction
"          
"              3 - Return from ring-0 tty with next interaction
"          
"              4 - Block
"          
"              5 - Non-tty wakeup
"          
"              6 - Tty wakeup (input)
"          
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

"

"   Definition of States

    equ initial,0
    equ blocked,1
    equ queued,2
    equ other,3
    equ processing,4
    equ max_response_state,4
    
"

"   Table definition macros

    equ state_element_words,2
    equ state_elements_per_word,4
    equ state_elements_per_entry,state_element_words*state_elements_per_word
    equ state_entry_shift,1
    equ state_element_shift,2
    
    set current_state_element,0

    macro   state_element
    maclist off save
    use trans
    org current_state_element
    dup state_element_words
    dec 0
    dupend
    maclist object
    org current_state_element
&R&(&=&x,1&[          vfd       &;,&]o9/&i&)
    set current_state_element,current_state_element+state_element_words
    maclist restore
    use .text.  
    &end
    
"
    use trans
    even
transition_table:
    use .text.

"   Initial (State 0)
    state_element   initial,initial,initial,processing,initial,initial,queued
"   Blocked (State 1)
    state_element   initial,initial,initial,initial,initial,other,queued
"   Queued (State 2)
    state_element   initial,other,initial,initial,initial,queued,initial
"   Other (State 3)
    state_element   initial,other,other,processing,blocked,other,other
"   Processing (State 4)
    state_element   initial,processing,other,initial,blocked,processing,initial

"
    join    /text/trans

    
trans_shift_table:
    dec 27,18,9,0
trans_mask_table:
    oct 777000000000
    oct 000777000000
    oct 000000777000
    oct 000000000777

"   macro to prepare for the day ...
    macro   read_clock
    rccl    sys_info$clock_,*
    &end
    

"

    entry   meter_response_time
    segdef  tc

"
    
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   Entry from outside of bound_traffic_control_priv
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

meter_response_time:

    push
    epbp3   tc_data$
    lda pr0|2,*         processid
    epp2    pr3|0,au            pr2 -> apte
    cmpa    pr2|apte.processid      can this possibly be right?
    tnz return          no way
    ldq pr0|4,*         transition number
    tsx7    tc
return: return

"

" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "
"
"   Entry from inside of bound_traffic_control_priv
"
" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "

    inhibit on  <+><+><+><+><+><+><+><+><+><+><+><+><+><+><+><+>

tc: stq pre_temp            transition number
    read_clock
    staq    pre_time

"   Validate current state and transition number.  Punt if either is
"   invalid by setting state to 0 (initial) and metering.
"   Find array element in transition table corresponding to
"   (current state, transition number)
    
    lda pr2|apte.current_response_state
    tmi invalid
    cmpa    max_response_state,dl
    tpnz    invalid
    eax1    0,al            x1=current state number
    als state_entry_shift       al=offset into trans table this state
    sta pre_temp+1
    lda pre_temp            transition number
    tmoz    invalid
    cmpa    MAX_TRANSITION,dl       
    tpnz    invalid
    eax6    0,al            x6=transition number
    ldq 0,dl
    lrl state_element_shift     al=word offset into array from state
    qrl 18-state_element_shift  qu=char offset into word
    ada pre_temp+1      al=word offset into array
    lda transition_table,al     array word
    ana trans_mask_table,qu     mask out proper character
    lxl0    trans_shift_table,qu    x0=shift count
    arl 0,x0            areg=new state
    cmpa    pr2|apte.current_response_state has state changed
    tze meter_and_exit      no
    als 0           check for invalid transition
    tze invalid         invalid
    ldx0    pr2|apte.wct_index
    epp0    pr3|0,x0            pr0 -> wcte this process
    eax0    0,al            x0=new state
"
"   Do special things based on previous state

    ldaq    pre_time
    sbaq    pr2|apte.last_response_state_time  aq=delta time in state
    tra prev_state_actions,x1*
prev_state_actions:
    arg check_next      initial
    arg update_think_time       blocked
    arg update_queue_time       queued
    arg check_next      other
    arg update_response_time    processing

update_think_time:
    cmpx6   TTY_WAKEUP,du       was the blocked time think time
    tnz check_next      no
    aos pr0|wcte.number_thinks  count number times thinking
    adaq    pr0|wcte.total_think_time   and update total time
    staq    pr0|wcte.total_think_time
    tra check_next

update_queue_time:
    aos pr0|wcte.number_queues  update count of queues
    adaq    pr0|wcte.total_queue_time   and total time queued
    staq    pr0|wcte.total_queue_time
    tra check_next


update_response_time:
    cmpx6   CALL_RING_0_TTY,du      was this the end of an interaction
    tnz check_next      no
    staq    pre_temp+2      save response time
    ldaq    pre_time            clock value on entry
    sbaq    pds$cpu_time        total cpu time
    sbaq    pds$virtual_delta       virtual cpu time
    sbaq    pr2|apte.begin_interaction_vcpu qreg=vcpu this interaction

"   Find proper bucket for this interaction.  Bucket boundaries (in terms
"   of vcpu time) are in the array tc_data$vcpu_response_bounds

    lda pr3|vcpu_response_bounds_size   al=highest array offset
find_vcpu_bucket:
    cmpq    pr3|vcpu_response_bounds-1,al   in this bucket
    tpl found_vcpu_bucket       yes
    sba 1,dl
    tpnz    find_vcpu_bucket        fall through if lowest bucket

"   Update statistics in APTE and WCTE

found_vcpu_bucket: 
    aos pr0|wcte.number_processing,al   count interactions
    aos pr2|apte.number_processing
    als 1           x2 (offset for double-word array)
    eax1    0,al            x0 = offset into double-word array
    lda 0,dl            aq = vcpu this interaction
    adaq    pr0|wcte.total_vcpu_time,x1
    staq    pr0|wcte.total_vcpu_time,x1
    ldaq    pre_temp+2      aq = response time this interaction
    adaq    pr0|wcte.total_processing_time,x1
    staq    pr0|wcte.total_processing_time,x1
    ldaq    pre_temp+2      aq = response time this interaction
    adaq    pr2|apte.total_processing_time
    staq    pr2|apte.total_processing_time
    

"
"   Do special things based on new state

check_next:
    tra next_state_actions,x0*
next_state_actions:
    arg state_update        initial
    arg state_update        blocked
    arg state_update        queued
    arg state_update        other
    arg mark_processing     processing

"   Note virtual cpu time at begin of interaction
"   We depend on running in the address space of the process
"   specified, which will be true since the transition is
"   a return from within that process
    
mark_processing:
    ldaq    pre_time
    sbaq    pds$cpu_time        real cpu time
    sbaq    pds$virtual_delta       virtual cpu time
    staq    pr2|apte.begin_interaction_vcpu

"
"   Update state, meter, and return

state_update:
    sxl0    pr2|apte.current_response_state
    ldaq    pre_time            clock at entry
    staq    pr2|apte.last_response_state_time
meter_and_exit: 
    read_clock
    sbaq    pre_time            metering overhead
    adaq    pr3|meter_response_time_overhead
    staq    pr3|meter_response_time_overhead
    aos pr3|meter_response_time_calls
    tra 0,x7

invalid:    
    ldx0    initial,du      reset state
    aos pr3|meter_response_time_invalid count these
    tra state_update
    
    inhibit off <-><-><-><-><-><-><-><-><-><-><-><-><-><-><-><->
"   
    include apte

          include   pxss_page_stack

    include response_transitions

    include tc_meters

    include wcte
        
    end

"This material is presented to ensure dissemination of scholarly and technical work. Copyright and all rights therein are retained by authors or by other copyright holders. All persons copying this information are expected to adhere to the terms and constraints invoked by each author's copyright. In most cases, these works may not be reposted without the explicit permission of the copyright holder."