?? RIGHT := 110 ??
?? NEWTITLE := 'NOS/VE Memory Management: Manage Page Table and ASIDs' ??
MODULE mmm$asid_page_table_manager;

{ PURPOSE
{   This module contains the memory modules for managing the page table and ASIDs.
{   There are three functional groups of procedures in this module. The modules are grouped
{   here since they are related:
{
{        ASID MANAGEMENT         - mmp$assign_asid
{                                  mmp$assign_specific_asid
{                                  mmp$free_asid
{                                  mmp$change_asid
{                                  mmp$reclaim_ast_entries
{
{        PAGE TABLE MANAGEMENT   - mmp$make_pt_entry
{                                  mmp$delete_pt_entry
{                                  clear_continue_bits
{                                  free_pt_entry_in_avail_queue
{
{        PAGE TABLE FULL MANAGER - mmp$process_page_table_full
{                                  build_asid_list
{                                  reassign_asid
{
{
?? TITLE := 'Global Declarations Referenced by This Module', EJECT ??
?? PUSH (LISTEXT := ON) ??
*copyc jmc$null_ajl_ordinal
*copyc mmc$first_transient_segment
*copyc mtc$job_fixed_segment
*copyc osc$processor_defined_registers
*copyc osc$purge_map_and_cache
*copyc mmt$active_segment_table
*copyc mmt$continue_bit_count
*copyc mmt$image_file
*copyc mmt$mainframe_wired_asid
*copyc mmt$make_pt_entry_status
*copyc mmt$page_frame_index
*copyc mmt$page_frame_queue_id
*copyc mmt$page_queue_list
*copyc mmt$pfti_array
*copyc mmt$pt_full_status
*copyc mmt$write_modified_pages_status
*copyc ost$heap
*copyc syt$monitor_status
?? POP ??
*copyc gfp$mtr_get_locked_fde_p
*copyc jmf$ijle_p
*copyc jmp$ijl_block_valid
*copyc jmp$unlock_ajl
*copyc jsp$recalculate_swapped_pages
*copyc mmp$asid
*copyc mmp$aste_pointer
*copyc mmp$asti
*copyc mmp$delete_last_pfti_from_array
*copyc mmp$find_next_pfti
*copyc mmp$free_image_pages_mtr
*copyc mmp$get_inhibit_io_status
*copyc mmp$get_max_sdt_pointer
*copyc mmp$get_sdt_entry_p
*copyc mmp$initialize_find_next_pfti
*copyc mmp$nudge_periodic_call
*copyc mmp$relink_page_frame
*copyc mmp$remove_page_from_jws
*copyc mmp$reset_find_next_pfti
*copyc mtp$error_stop
*copyc tmp$find_next_xcb
*copyc dsv$ssr_sdte
*copyc jmv$ijl_p
*copyc jmv$max_ajl_ordinal_in_use
*copyc jmv$null_ijl_ordinal
*copyc jmv$system_ijl_ordinal
*copyc mmv$ast_p
*copyc mmv$async_work
*copyc mmv$image_file
*copyc mmv$multiple_caches
*copyc mmv$multiple_page_maps
*copyc mmv$pft_p
*copyc mmv$pfti_array_p
*copyc mmv$pt_length
*copyc mmv$pt_p
*copyc mtv$monitor_segment_table
*copyc mtv$nos_segment_table_p

*if $true(mmc$debug)
*copyc mmv$test_reassign_asid
*ifend

*copyc osv$page_size
?? TITLE := 'Global Statistics Kept By Module', EJECT ??
{ the following is used for debugging trace information:

  PROCEDURE [INLINE] trace
    (    id: integer;
         inc: integer);

    mmv$aptm_trace [id] := mmv$aptm_trace [id] + inc;
  PROCEND trace;

  CONST
    mmc$ap_low_asids = 1,
    mmc$ap_no_asids = 2,
    mmc$ap_ast_reset = 3,
    mmc$ap_free_aste = 4,
    mmc$ap_assign = 5,
    mmc$ap_assign_specific = 6,
    mmc$ap_casid_swapped_job = 7,
    mmc$ap_casid_monitor = 8,
    mmc$ap_casid_recovery = 9,
    mmc$ap_casid_template = 10,
    mmc$ap_casid_global = 11,
    mmc$ap_casid_job = 12,
    mmc$ap_reclaim_asids = 13,
    mmc$ap_mpte_full = 14,
    mmc$ap_mpte_recovered = 15,
    mmc$ap_mpte_rec1 = 16,
    mmc$ap_mpte_rec2 = 17,
    mmc$ap_ptf_called = 18,
    mmc$ap_ptf_tried = 19,
    mmc$ap_ptf_failed = 20,
    mmc$ap_ptf_remove = 21,
    mmc$ap_rea_called = 22,
    mmc$ap_rea_in_free = 23,
    mmc$ap_rea_mpte_fail = 24,
    mmc$ap_rea_ok = 25,
    mmc$ap_rea_ok1 = 26,
    mmc$ap_rea_ok2 = 27,
    mmc$ap_rea_ok3 = 28,
    mmc$ap_rea_ok4 = 29,
    mmc$ap_rea_fail = 30,
    mmc$ap_rea_fail1 = 31,
    mmc$ap_rea_fail2 = 32,
    mmc$ap_rea_quit = 33,
    mmc$ap_unused_34 = 34,
    mmc$ap_unused_35 = 35,
    mmc$ap_rea_make_pt_entry = 36,
    mmc$ap_ba_freed_terj = 37;

{ The following table keeps statistics on page table full procesing.

  TYPE
    mmt$pt_full_trace_info = record
      timestamp: integer,
      changed_asid: 0 .. 0ffffffff(16),
      failed: 0 .. 0ffffffff(16),
      pass: array [reassign_pass] of 0 .. 0ffffffff(16),
      last_sva: ost$system_virtual_address,
      index: 0 .. 65535,
      asid: array [0 .. 127] of record
        old: ost$asid,
        new: ost$asid,
      recend,
    recend;

  VAR
    mmv$pt_full_trace: [XDCL] mmt$pt_full_trace_info,
    mmv$aptm_trace: [XDCL] array [0 .. 50] of 0 .. 0ffffffff(16);

?? TITLE := 'Global Declarations Declared by This Module', EJECT ??

  VAR
    changing_asid: ost$asid := 0,
    changing_aste_p: ^mmt$active_segment_table_entry := NIL,
    mmv$continue_bit_count_p: [XDCL, #GATE] ^mmt$continue_bit_count,
    mmv$max_template_segment_number: [XDCL, #GATE] integer := mmc$first_loader_predefined_seg,
    mmv$mf_wired_asid_p: [XDCL, #GATE] ^mmt$mainframe_wired_asid := NIL,
    mmv$number_free_astes: [XDCL, #GATE] integer,
    mmv$pages_to_dump_p: [XDCL, #GATE] ^packed array [0 .. * ] of boolean := NIL,
    mmv$pt_search: integer {4 .. 32} := 32,
    mmv$time_changed_global_asid: [XDCL] ost$free_running_clock := 0ffffffffffff(16),
    mmv$time_changed_template_asid: [XDCL] ost$free_running_clock := 0ffffffffffff(16);

?? TITLE := 'Inline Procedures From Common Decks', EJECT ??
*copyc mmp$purge_all_cache_map
*copyc mmp$purge_all_page_seg_map

?? TITLE := 'ASID MANAGEMENT' ??
?? NEWTITLE := '[XDCL] mmp$assign_asid', EJECT ??
{ PURPOSE:
{   This procedure is called to find an available entry in the AST table, assign the entry and
{   return an ASID for the entry. If no AST entry is available, the routine HALTS. This should never
{   happen since there are more ASIDs than page frames, and ASIDs not currently being used by any
{   page frames can be reassigned.
{
{ DESIGN
{   This procedure searches the AST for an entry that is free and has been free since before
{   the last time CACHE and MAP were purged. If necessary, this routine will reclaim AST entries
{   assigned to segments/files if no pages are currently assigned to the segment

  VAR
    last_purge_time: [STATIC] integer := 1,
    next_asti: [STATIC] mmt$ast_index := 1;


  PROCEDURE [XDCL] mmp$assign_asid
    (VAR asid: ost$asid;
     VAR asti: mmt$ast_index;
     VAR aste_p: ^mmt$active_segment_table_entry);

    VAR
      zaste_p: ^mmt$active_segment_table_entry;

{ If there are NO free entries, call reclaim unused entries right now since an entry MUST be
{ assigned. If the number of free entries is too small, force a call to MM periodic to do
{ the reclaiming as soon as possible.

    IF mmv$number_free_astes < 30 THEN {30 is arbitrary number}
      IF mmv$number_free_astes = 0 THEN
        trace (mmc$ap_no_asids, 1);
        mmp$reclaim_ast_entries (0);
        IF mmv$number_free_astes = 0 THEN
          mtp$error_stop ('MM26 - AST full');
        IFEND;
      ELSE
        trace (mmc$ap_low_asids, 1);
        mmv$async_work.reclaim_astes := TRUE;
        mmp$nudge_periodic_call;
      IFEND;
    IFEND;

    mmv$number_free_astes := mmv$number_free_astes - 1;

{ Find and assign a free AST entry. CACHE and MAP must be purged when the AST assignment algorithm wraps
{ around and reassigns entries that may still be in cache or map. Note that free entries cannot be reused
{ until a purge occurs.

    REPEAT
      next_asti := next_asti - 1;
      IF next_asti = 0 THEN
        next_asti := UPPERBOUND (mmv$ast_p^);
        last_purge_time := #FREE_RUNNING_CLOCK (0);
        mmp$purge_all_cache_map;
        trace (mmc$ap_ast_reset, 1);
      IFEND;
    UNTIL NOT mmv$ast_p^ [next_asti].in_use AND (mmv$ast_p^ [next_asti].time_freed < last_purge_time);

    asti := next_asti;
    aste_p := ^mmv$ast_p^ [next_asti];
    asid := aste_p^.asid;
    aste_p^.in_use := TRUE;
    trace (mmc$ap_assign, 1);

*if $true(mmc$debug)
      mmp$aste_pointer (asid, zaste_p);
      IF zaste_p <> aste_p THEN
        mtp$error_stop ('MM - bad ASID in assign  ASID');
      IFEND;
*ifend

  PROCEND mmp$assign_asid;
?? TITLE := '[XDCL] mmp$assign_specific_asid', EJECT ??
{ PURPOSE:
{   This procedure is used by the Job Swapper to reclaim a specific ASID for swapin. No cache/map
{   purging is required since the ASID will be reclaimed ONLY if it is currently free AND has not
{   been used by another job while the reclaiming job was swapped out. It is important
{   that the ASID not have been used by another job because cache/map is not purged
{   as part of swapin.
{
{ DESIGN:
{   To reclaim an ASID, simply reset the in_use field. Counts of AST entries in
{   use are adjusted as required.
{
{ NOTE:
{   The caller is responsible for verifying that the ASID can correctly be reclaimed.

  PROCEDURE [XDCL] mmp$assign_specific_asid
    (    aste_p: ^mmt$active_segment_table_entry);

    VAR
      asti: mmt$ast_index,
      zaste_p: ^mmt$active_segment_table_entry;

    mmv$number_free_astes := mmv$number_free_astes - 1;
    trace (mmc$ap_assign_specific, 1);

*if $true(mmc$debug)
      mmp$asti (aste_p^.asid, asti);
      zaste_p := ^mmv$ast_p^ [asti];
      IF zaste_p <> aste_p THEN
        mtp$error_stop ('MM - bad ASID in assign specific ASID');
      IFEND;
      IF aste_p^.in_use THEN
        mtp$error_stop ('MM - assign specific of already in use');
      IFEND;
*ifend

    aste_p^.in_use := TRUE;

  PROCEND mmp$assign_specific_asid;
?? TITLE := '[XDCL] mmp$free_asid', EJECT ??
{ PURPOSE:
{   This procedure is called to free an ASID. The AST entry corresponding to the ASID is marked as free.
{
{ DESIGN:
{   To free an ASID, the AST entry is marked as not in use and the ASID is saved. The
{   time the entry was freed is saved in the AST. The timestamp is used by mmp$assign to
{   correctly manage cache and map purges. An ASID cannot be assigned to a different segment
{   until cache and map are purged. Failing to purge the cache or map will cause failures since
{   stale data may be used.


  PROCEDURE [XDCL] mmp$free_asid
    (    asid: ost$asid;
         aste_p: ^mmt$active_segment_table_entry);

    VAR
      zaste_p: ^mmt$active_segment_table_entry;

    IF NOT aste_p^.in_use OR (aste_p^.pages_in_memory <> 0) THEN
      mtp$error_stop ('MM42 - error in free ast entry');
    IFEND;

*if $true(mmc$debug)
      mmp$aste_pointer (asid, zaste_p);
      IF zaste_p <> aste_p THEN
        mtp$error_stop ('MM - bad ASID in free ASID');
      IFEND;
*ifend

    mmv$number_free_astes := mmv$number_free_astes + 1;

    aste_p^.in_use := FALSE;
    aste_p^.asid := asid;
    aste_p^.time_freed := #FREE_RUNNING_CLOCK (0);

*if $true(mmc$debug)
    IF mmv$test_reassign_asid THEN
      aste_p^.sfid.file_hash := 255;
    IFEND;
*ifend

    trace (mmc$ap_free_aste, 1);

  PROCEND mmp$free_asid;
?? TITLE := '[XDCL] mmp$change_asid', EJECT ??
{ PURPOSE:
{   This procedure will change an ASID from an old value to a new value.
{   All affected Segment tables, FDEs, and system tables are updated as required.
{
{ DESIGN:
{   This procedure searches all segment tables of all tasks that could be using the ASID.
{   The number of tasks and jobs to search depends on the attributes of the segment/file that is using
{   the ASID. If a job that should be searched is swapped out, a flag is set in the IJL
{   to notify the swapper that ASIDs must be fixed on swap-in.
{
{   The following table shows all the types of ASIDs that can be in SDTs and the way these
{   ASIDs are located when they change (ie., what is searched). Note that the table is
{   ordered from easiest-to-change to hardest-to-change - the number at the beginning of the line
{   indicates preferred order for reassignment.
{
{       ASID                      NOT SWAPPED                   SWAPPED
{ 1   local file/transient   search all XCBs in AST.IJLO   dsw_job_asid_changed
{
{ 2   perm file in JWS       search all XCBs in AST.IJLO   dsw_job_asid_changed
{
{ 3   perm file in shared Q  search all XCBs               mmv$time_changed_global_asid > timestamp
{
{ 3   template asid          search all XCBs               mmv$time_changed_template_asid  > timestamp
{


  PROCEDURE [XDCL] mmp$change_asid
    (    aste_p: ^mmt$active_segment_table_entry;
         old_asid: ost$asid;
         new_asid: ost$asid;
         new_asti: mmt$ast_index);

    VAR
      cell_p: ^cell,
      fde_p: gft$file_desc_entry_p,
      ijle_p: ^jmt$initiated_job_list_entry,
      max_segnum: ost$segment,
      recovery: boolean,
      segnum: integer, {allow negative numbers}
      sdt_p: mmt$max_sdt_p,
      ste_p: ^mmt$segment_descriptor,
      xcb_p: ^ost$execution_control_block,
      xcb_state: tmt$find_next_xcb_state;



{ Get a pointer to the XCB of the first task whose segment table is to be scanned. If the job that
{ owns the file is swapped, this pointer will be NIL. If the job is partially swapped, this
{ request will assign an AJL ordinal. Note that the actual XCBs scanned will be either all XCBs in
{ the system or just XCBs in a specific job.

    ijle_p := jmf$ijle_p (aste_p^.ijl_ordinal);
    IF (aste_p^.ijl_ordinal = jmv$system_ijl_ordinal) AND
          (aste_p^.queue_id < mmc$pq_job_base) AND
          (aste_p^.sfid.residence = gfc$tr_system) THEN
      tmp$find_next_xcb (tmc$fnx_system, NIL, jmv$null_ijl_ordinal, xcb_state, xcb_p);
    ELSE
      tmp$find_next_xcb (tmc$fnx_job, ijle_p, aste_p^.ijl_ordinal, xcb_state, xcb_p);
    IFEND;

    recovery := aste_p^.sfid.residence = gfc$tr_system_wait_recovery;
    IF recovery THEN
      trace (mmc$ap_casid_recovery, 1);
    IFEND;

{ If the FDE is accessible (job not swapped and file recovered OR tables in mainframe wired),
{ get a pointer to the FDE and update the ASTI. (If the job is swapped, the FDE will be updated
{ on the next swap-in). If the ASID belongs to a job fixed segment, fix the ASID in the
{ monitor segment table.

    IF ((xcb_p <> NIL) AND (NOT recovery)) OR
          (aste_p^.sfid.residence = gfc$tr_system) THEN
      gfp$mtr_get_locked_fde_p (aste_p^.sfid, ijle_p, fde_p);
      fde_p^.asti := new_asti;
    IFEND;

    IF old_asid = ijle_p^.job_fixed_asid THEN
      ijle_p^.job_fixed_asid := new_asid;
      IF ijle_p^.ajl_ordinal <> jmc$null_ajl_ordinal THEN
        mtv$monitor_segment_table.st [ijle_p^.ajl_ordinal + mtc$job_fixed_segment].ste.asid := new_asid;
      IFEND;
    IFEND;

{ If the job is swapped, then set the delayed swapin flag and fix the ASIDs when the
{ job next swaps in.

    IF xcb_p = NIL THEN
      trace (mmc$ap_casid_swapped_job, 1);
      ijle_p^.delayed_swapin_work := ijle_p^.delayed_swapin_work +
            $jmt$delayed_swapin_work [jmc$dsw_job_asid_changed];


{ If the segment exists in monitor's address space ONLY, nothing needs to be done here. Code
{ further down will fix monitor's segment table. Unlock the ajl set by find_next_xcb.

    ELSEIF (NOT recovery) AND (fde_p^.file_kind = gfc$fk_monitor_only_unnamed) THEN
      trace (mmc$ap_casid_monitor, 1);
      jmp$unlock_ajl (ijle_p);

{ The segment may be in segment tables in one or more jobs in memory. Fix the ASID in all jobs
{ that could be referencing the file.

    ELSE
      IF (NOT recovery) AND (fde_p^.flags.global_template_file) THEN
        mmv$time_changed_template_asid := #FREE_RUNNING_CLOCK (0);
        max_segnum := mmv$max_template_segment_number;
        trace (mmc$ap_casid_template, 1);

      ELSE
        max_segnum := 4095;
        IF (aste_p^.sfid.residence = gfc$tr_system) OR recovery THEN
          IF aste_p^.queue_id >= mmc$pq_job_base THEN
            trace (mmc$ap_casid_job, 1);
          ELSE
            mmv$time_changed_global_asid := #FREE_RUNNING_CLOCK (0);
            trace (mmc$ap_casid_global, 1);
          IFEND;
        IFEND;
      IFEND;


    /fix_ste_loop/
      WHILE xcb_p <> NIL DO
        mmp$get_max_sdt_pointer (xcb_p, sdt_p);
        segnum := xcb_p^.xp.segment_table_length;
        IF segnum > max_segnum THEN
          segnum := max_segnum;
        IFEND;
        WHILE segnum >= 0 DO
          IF sdt_p^.st [segnum].ste.asid = old_asid THEN
            sdt_p^.st [segnum].ste.asid := new_asid;
            sdt_p^.st [segnum].asti := new_asti;
          IFEND;
          segnum := segnum - 1;
        WHILEND;
        tmp$find_next_xcb (tmc$fnx_continue, NIL, jmv$null_ijl_ordinal, xcb_state, xcb_p);
      WHILEND /fix_ste_loop/;

    IFEND;


{ Change the ASID in monitor's segment table and NOS segment table if segment is not pageable.

    IF (aste_p^.queue_id = mmc$pq_wired) THEN
      cell_p := ^mtv$monitor_segment_table;
      #PURGE_BUFFER (osc$pva_purge_segment_cache, cell_p);
      FOR segnum := 0 TO mtc$job_fixed_segment - 1 DO
        IF mtv$monitor_segment_table.st [segnum].ste.asid = old_asid THEN
          mtv$monitor_segment_table.st [segnum].ste.asid := new_asid;
        IFEND;
      FOREND;
      FOR segnum := 0 TO UPPERBOUND (mtv$nos_segment_table_p^.st) DO
        IF mtv$nos_segment_table_p^.st [segnum].ste.asid = old_asid THEN
          mtv$nos_segment_table_p^.st [segnum].ste.asid := new_asid;
        IFEND;
      FOREND;
    IFEND;


{ Purge the segment file and page file to remove any entries that have the old ASID.

    mmp$purge_all_page_seg_map;

  PROCEND mmp$change_asid;
?? TITLE := '[XDCL] mmp$reclaim_ast_entries', EJECT ??
{ PURPOSE
{   This routine is called to search through the AST and free entries that
{   have no pages in memory. The routine is normally called by mmp$periodic_call
{   with accounting being charged to the system.
{   If an AST full condition is imminent, this routine is called by mmp$assign_asid - a user will incur
{   the overhead necessary to try to keep the system from hanging.
{
{ DESIGN:
{   An ASID can be reclaimed if there are no pages in memory using the ASID.  Since there
{   are more ASID than page frames, it should always be possible to reclaim enough ASIDs.
{   When an ASID is reclaimed, the ASID in all segment tables that contain the ASID is
{   zeroed out. Device manager is also notified to update its value of the ASID. Note
{   that ASIDs belonging to swapped out jobs may be reclaimed while the job is swapped out.
{   In this case, the job swapper is responsible for fixing the segment tables
{   at swap-in time.
{

  PROCEDURE [XDCL] mmp$reclaim_ast_entries
    (    asti_that_cannot_be_freed: mmt$ast_index);

    VAR
      asid: ost$asid,
      end_of_table_seen: boolean,
      next_aste_index: mmt$ast_index,
      number_astes_reclaimed: integer;

    next_aste_index := next_asti;
    end_of_table_seen := FALSE;
    number_astes_reclaimed := 0;
    trace (mmc$ap_reclaim_asids, 1);

  /reclaim_loop/

    WHILE number_astes_reclaimed < 30 DO {30 is arbitrary number}
      next_aste_index := next_aste_index - 1;
      IF next_aste_index = 0 THEN
        IF end_of_table_seen THEN
          RETURN;
        IFEND;
        end_of_table_seen := TRUE;
        next_aste_index := UPPERBOUND (mmv$ast_p^);
      IFEND;
      IF mmv$ast_p^ [next_aste_index].in_use AND (mmv$ast_p^ [next_aste_index].pages_in_memory = 0) AND
            (asti_that_cannot_be_freed <> next_aste_index) THEN
        mmp$asid (next_aste_index, asid);
        IF (jmp$ijl_block_valid (mmv$ast_p^ [next_aste_index].ijl_ordinal)) AND
              (jmv$ijl_p.block_p^[mmv$ast_p^ [next_aste_index].ijl_ordinal.block_number].index_p^
              [mmv$ast_p^ [next_aste_index].ijl_ordinal.block_index].entry_status <> jmc$ies_entry_free) THEN
          mmp$change_asid (^mmv$ast_p^ [next_aste_index], asid, 0, 0);
        IFEND;
        mmp$free_asid (asid, ^mmv$ast_p^ [next_aste_index]);
        number_astes_reclaimed := number_astes_reclaimed + 1;
      IFEND;

    WHILEND /reclaim_loop/;

    mmp$purge_all_cache_map;
    last_purge_time := #FREE_RUNNING_CLOCK (0);

  PROCEND mmp$reclaim_ast_entries;
?? OLDTITLE ??
?? TITLE := 'PAGE TABLE MANAGEMENT' ??
?? NEWTITLE := '[XDCL] mmp$make_pt_entry', EJECT ??
{ Purpose:
{   This routine makes an entry in the system page table after
{   checking to make sure a page table entry for the page does
{   not already exist.
{ Input:
{   sva - SVA of any byte in the page
{   pfti - index of page frame to assign to the page
{   aste_p - pointer to AST entry for page
{   pfte_p - pointer to PFT entry for page. ONLY THE PTI ENTRY IS USED. IMPORTANT
{            because REASSIGN_ASID passes a dummy pfte_p.
{ Output:
{   pti - page_table_index of PT entry assigned to the page is stored into the PFT
{         entry located via the PFTE_P input parameter.
{ Error Codes:
{   status - The following errors may be detected by this proc
{      page table full
{      page table entry exists

  VAR
    mmv$page_table_miss_count: [XDCL] array [1 .. 34] of integer;


  PROCEDURE [XDCL] mmp$make_pt_entry
    (    sva: ost$system_virtual_address;
         pfti: mmt$page_frame_index;
         aste_p: ^mmt$active_segment_table_entry;
         pfte_p: ^mmt$page_frame_table_entry;
     VAR mpt_status: mmt$make_pt_entry_status);

    VAR
      cbc_p: ^mmt$continue_bit_count,
      count: 0 .. 33,
      fcount: 0 .. 31,
      found: boolean,
      hcount: 1 .. 32,
      pt_p: ^ost$page_table,
      pte: ost$page_table_entry,
      pti: integer,
      save_pti: integer,
      starting_pti: integer;

{ Calculate the hash index for the page table entry and determine if the page already exists. Return an error
{ code if an entry already exists.

    #HASH_SVA (sva, starting_pti, hcount, found);
    IF found THEN
      mpt_status := mmc$mpt_page_already_exists;
      RETURN;
    IFEND;
    starting_pti := starting_pti - hcount + 1;
    IF starting_pti < 0 THEN
      starting_pti := starting_pti + mmv$pt_length;
    IFEND;

{ Find an available slot for the new page table entry. Set 'continue' bits as required.  Return error if no
{ space is found within 32 entries. Note that early in deadstart, the continue bit array is not allocated.
{ During this time the page table is completely filled with entries with 'C' set.

    count := 1;
    pt_p := mmv$pt_p;
    cbc_p := mmv$continue_bit_count_p;
    pti := starting_pti;
    WHILE (pt_p^ [pti].pageid.asid <> 0) AND (count < mmv$pt_search + 1) DO
      cbc_p^ [pti] := cbc_p^ [pti] + 1;
      IF cbc_p^ [pti] = 1 THEN
        pt_p^ [pti].c := TRUE;
      IFEND;
      count := count + 1;
      pti := pti + 1;
      IF pti = mmv$pt_length THEN
        pti := 0;
      IFEND;
    WHILEND;
    mmv$page_table_miss_count [count] := mmv$page_table_miss_count [count] + 1;


{ If no entry was found within the required 32 entries,try to free some entries in the page table
{ in the area searched. Clear unnecessary continue bits that were set in the above loop.
{ Exit if not possible to make entry in the page table.

    IF count = (mmv$pt_search + 1) THEN
      trace (mmc$ap_mpte_full, 1);
      save_pti := pti;
      free_pt_entry_in_avail_queue (starting_pti, pfti, pti, fcount, found);
      clear_continue_bits (save_pti, (mmv$pt_search + 1) - fcount);
      IF NOT found THEN
        mpt_status := mmc$mpt_page_table_full;
        RETURN;
      IFEND;
      mmv$page_table_miss_count [34] := mmv$page_table_miss_count [34] + 1;
      trace (mmc$ap_mpte_recovered, 1);
    IFEND;


{ Make the new page table entry, preserving the 'continue' bit in the old page table entry.

    pte.v := FALSE;
    pte.c := pt_p^ [pti].c;
    pte.u := TRUE;
    pte.m := FALSE;
    pte.pageid.asid := sva.asid;
    pte.pageid.pagenum :=
    #SHIFT (sva.offset, -9);
    pte.rma :=
    #SHIFT (pfti * osv$page_size, -9);
    pt_p^ [pti] := pte;
    pfte_p^.pti := pti;
    IF NOT aste_p^.in_use THEN
      mtp$error_stop ('MM--MAKE_PT_ENTRY--AST NOT IN USE');
    IFEND;

{ A non-zero changing_asid indicates this procedure was called by page table full processing.  The page is a
{ new page and should be linked to the segment only if the call to this procedure was NOT made for page table
{ full processing.

    IF changing_asid = 0 THEN
      mmp$link_page_to_segment (pfti, pfte_p, aste_p);
    IFEND;
    aste_p^.pages_in_memory := aste_p^.pages_in_memory + 1;
    mmv$pages_to_dump_p^ [pfti] := aste_p^.include_pages_in_dump;
    mpt_status := mmc$mpt_done;

  PROCEND mmp$make_pt_entry;
?? TITLE := '[XDCL] mmp$delete_pt_entry', EJECT ??
{ Purpose:
{   This routine deletes a page table entry for a page.
{
{ Input:
{   pfti - page frame table index of frame assigned to the page
{
{ Output:
{   none
{
{ NOTE !!! Caller must either clear the 'v' bit & purge map in the page table or otherwise ensure
{   that the entry being deleted is NOT being referenced by another CPU in a multi-CPU
{   configuration.
{   The unlink_page_from_segment parameter is used to indicate whether the page should be unlinked
{   from the segment.  This parameter should be FALSE only from a few calls in page table full
{   processing.  Because page table full processing creates two page table entries for one page frame
{   for a short time, the duplicate page table entry must be deleted without unlinking the page frame
{   from the segment.


  PROCEDURE [XDCL] mmp$delete_pt_entry
    (    pfti: mmt$page_frame_index;
         unlink_page_from_segment: boolean);

    VAR
      aste_p: ^mmt$active_segment_table_entry,
      count: integer,
      found: boolean,
      pfte_p: ^mmt$page_frame_table_entry,
      pte_p: ^ost$page_table_entry,
      pti: integer;


{ Entry has been found.  Delete the entry by clearing the valid bit and setting ASID to zero.

    pfte_p := ^mmv$pft_p^ [pfti];
    #HASH_SVA (pfte_p^.sva, pti, count, found);
    pti := pfte_p^.pti;
    pte_p := ^mmv$pt_p^ [pti];

*if $true(mmc$debug)
      IF (pti <> pfte_p^.pti) OR NOT found THEN
        mtp$error_stop ('MM - bad PFT.pti on delete pte');
      IFEND;
      IF (pte_p^.pageid.asid <> pfte_p^.sva.asid) OR (pte_p^.pageid.pagenum * 512 <> pfte_p^.sva.offset) OR
            (pte_p^.rma * 512 <> (pfti * osv$page_size)) THEN
        mtp$error_stop ('MM - illegal delete pte');
      IFEND;
*ifend

    pte_p^.v := FALSE;
    pte_p^.pageid.asid := 0;

{ Clear continue bits if necessary.
    IF (count > 1) THEN
      clear_continue_bits (pti, count);
    IFEND;

{ Decrement the 'pages in memory' field of the AST.
    aste_p := pfte_p^.aste_p;
    IF aste_p^.pages_in_memory = 0 THEN
      mtp$error_stop ('MM - delete pte, no pages in memory');
    IFEND;
    aste_p^.pages_in_memory := aste_p^.pages_in_memory - 1;

{ Unlink page from segment is true from all callers except a few specific calls from process page table full.
    IF unlink_page_from_segment THEN
      mmp$unlink_page_from_segment (pfte_p, aste_p);
    IFEND;

  PROCEND mmp$delete_pt_entry;

?? TITLE := 'clear_continue_bits', EJECT ??

  PROCEDURE [INLINE] clear_continue_bits
    (    xpti: ost$page_table_index;
         count: integer);

    VAR
      i: integer,
      pti: integer;

{ Clear 'continue' bits as required.  Decrement the count of the number of times the
{ bit is 'set'. When the count goes to zero, clear the continue bit in the page table.

    pti := xpti;
    FOR i := 2 TO count DO
      pti := pti - 1;
      IF pti < 0 THEN
        pti := mmv$pt_length - 1;
      IFEND;
      mmv$continue_bit_count_p^ [pti] := mmv$continue_bit_count_p^ [pti] - 1;
      IF mmv$continue_bit_count_p^ [pti] = 0 THEN
        mmv$pt_p^ [pti].c := FALSE;
      IFEND;
    FOREND;

  PROCEND clear_continue_bits;

?? TITLE := 'mmp$link_page_to_segment', EJECT ??
{ Purpose:
{   This procedure is called from mmp$make_pt_entry to insert the page frame into the
{   thread which links all pages of a segment that are in memory.  There must be NO OTHER CALLERS
{   of this procedure, or the integrity of the links will be destroyed.

  PROCEDURE [XDCL, INLINE] mmp$link_page_to_segment
    (    pfti: mmt$page_frame_index;
         pfte_p: ^mmt$page_frame_table_entry;
         aste_p: ^mmt$active_segment_table_entry);

{ Debug code

    IF (pfte_p^.segment_link.fwd <> 0) OR (pfte_p^.segment_link.bkw <> 0) THEN
      mtp$error_stop ('LINK PAGE TO SEGMENT ERROR');
    IFEND;

    IF (aste_p^.pages_in_memory = 0) AND ((aste_p^.pft_link.bkw <> 0) OR (aste_p^.pft_link.fwd <> 0)) THEN
      mtp$error_stop ('LINK PAGE TO SEGMENT ERROR--AST');
    IFEND;

{ End debug code

    IF aste_p^.pft_link.fwd = 0 THEN
      aste_p^.pft_link.fwd := pfti;
      aste_p^.pft_link.bkw := pfti;
    ELSE
      mmv$pft_p^ [aste_p^.pft_link.bkw].segment_link.fwd := pfti;
      pfte_p^.segment_link.bkw := aste_p^.pft_link.bkw;
      aste_p^.pft_link.bkw := pfti;
    IFEND;

  PROCEND mmp$link_page_to_segment;

?? TITLE := 'mmp$unlink_page_from_segment', EJECT ??
{ Purpose:
{   This procedure is called from mmp$delete_pt_entry to remove the page frame from the
{   thread which links all pages of a segment that are in memory.  There must be NO OTHER CALLERS
{   of this procedure, or the integrity of the links will be destroyed.

  PROCEDURE [XDCL, INLINE] mmp$unlink_page_from_segment
    (    pfte_p: ^mmt$page_frame_table_entry;
         aste_p: ^mmt$active_segment_table_entry);


    IF pfte_p^.segment_link.fwd = 0 THEN
      aste_p^.pft_link.bkw := pfte_p^.segment_link.bkw;
    ELSE
      mmv$pft_p^ [pfte_p^.segment_link.fwd].segment_link.bkw := pfte_p^.segment_link.bkw;
    IFEND;

    IF pfte_p^.segment_link.bkw = 0 THEN
      aste_p^.pft_link.fwd := pfte_p^.segment_link.fwd;
    ELSE
      mmv$pft_p^ [pfte_p^.segment_link.bkw].segment_link.fwd := pfte_p^.segment_link.fwd;
    IFEND;

    pfte_p^.segment_link.fwd := 0;
    pfte_p^.segment_link.bkw := 0;

{ Debug code

    IF (changing_asid = 0) AND (aste_p^.pages_in_memory = 0) AND
          ((aste_p^.pft_link.bkw <> 0) OR (aste_p^.pft_link.fwd <> 0)) THEN
      mtp$error_stop ('LINK PAGE TO SEGMENT ERROR--AST');
    IFEND;

{ End deubg

  PROCEND mmp$unlink_page_from_segment;

?? TITLE := ' free_pt_entry_in_avail_queue ', EJECT ??
{ PURPOSE
{   This procedure is used in page table full processing. It scans the 32 page table entries starting
{   at the specified hash index.If an entry in the AVAIL queue is found, it is freed.
{
{ INPUT:
{    initial_pti: starting hash index
{    initial_pfti: PFT index of page frame that is being entered into page table
{            A PT entry belonging to this page will NOT be deleted. (required for page table
{            full processing - see REASSIGN_ASID.
{ OUTPUT:
{    pti : index to freed entry (undefined if no entry freed)
{    count: number of entries searched (- 1) before finding entry to free (0 = none found)
{    freed: boolean to indicate if entry freed

  PROCEDURE free_pt_entry_in_avail_queue
    (    initial_pti: integer;
         initial_pfti: mmt$page_frame_index;
     VAR xpti: integer;
     VAR xcount: 0 .. 31;
     VAR freed: boolean);

    VAR
      count: 0 .. 31,
      pfte: mmt$page_frame_table_entry,
      pfti: mmt$page_frame_index,
      pte_p: ^ost$page_table_entry,
      pti: integer;

{ Scan the 32 entries and free the first entry found that is in the AVAIL queue. NOTE:
{ link the page frame to the FREE queue only if the PFT.PTI field is correct. If its not
{ correct, then the entry must belong to segment that is having its ASID reassigned as
{ a result of a PAGE_TABLE_FULL condition. Also, an entry using the SAME page frame as the one
{ for which a new page table entry is being made cannot be deleted. This case arises during page
{ table full processing - both entries must exist at the same time in order for the
{ page table full algorithms to work.

    pti := initial_pti;
    FOR count := 0 TO mmv$pt_search - 1 DO
      pte_p := ^mmv$pt_p^ [pti];
      IF NOT pte_p^.m THEN
        pfti := (pte_p^.rma * 512) DIV osv$page_size;
        IF (pfti >= LOWERBOUND (mmv$pft_p^)) AND (pfti <= UPPERBOUND (mmv$pft_p^)) AND
              (mmv$pft_p^ [pfti].queue_id = mmc$pq_avail) AND (pfti <> initial_pfti) THEN
          trace (mmc$ap_mpte_rec1, 1);
          IF mmv$pft_p^ [pfti].pti = pti THEN
            trace (mmc$ap_mpte_rec2, 1);
            mmp$delete_pt_entry (pfti, TRUE);
            mmp$relink_page_frame (pfti, mmc$pq_free);
          ELSEIF (pte_p^.pageid.asid = changing_asid) THEN
            pfte := mmv$pft_p^ [pfti];
            mmv$pft_p^ [pfti].sva.asid := pte_p^.pageid.asid;
            mmv$pft_p^ [pfti].pti := pti;
            mmv$pft_p^ [pfti].aste_p := changing_aste_p;
            mmp$delete_pt_entry (pfti, FALSE);
            mmv$pft_p^ [pfti] := pfte;
          ELSE
            mtp$error_stop ('MM - PT/PFT mismatch');
          IFEND;
          xcount := count;
          xpti := pti;
          freed := TRUE;
          RETURN;
        IFEND;
      IFEND;
      pti := pti + 1;
      IF pti = mmv$pt_length THEN
        pti := 0;
      IFEND;
    FOREND;

    xcount := 0;
    freed := FALSE;

  PROCEND free_pt_entry_in_avail_queue;
?? OLDTITLE ??
?? TITLE := 'PAGE TABLE FULL MANAGER' ??
?? NEWTITLE := '[XDCL] mmp$process_pt_full', EJECT ??
{ PURPOSE:
{   This procedure is called to try to recover from a 'page table full' condition.
{   The procedure does the following:
{     . try to reassign an ASID in the portion of the PT that is full.
{     . try to free or write to disk all pages in the part of the PT that
{       is full. (this step is attempted only if the previous step failed).
{
{ INPUT:
{     sva: SVA that caused PT full.
{
{ OUTPUT:
{     new_asid: new asid assigned
{     new_asti: asti of reassigned ASID
{     new_aste_p: aste_p of reassigned ASID
{     pt_full_status: indicates status of reassignment


  PROCEDURE [XDCL] mmp$process_page_table_full
    (    sva: ost$system_virtual_address;
     VAR new_asid: ost$asid;
     VAR new_asti: mmt$ast_index;
     VAR new_aste_p: ^mmt$active_segment_table_entry;
     VAR pt_full_status: mmt$pt_full_status);

    VAR
      asidl: asid_list_index_type,
      asidlmax: asid_list_index_type,
      asidt: [XDCL, STATIC] asid_list, {xdcled for debug only}
      asti_that_cannot_be_freed: mmt$ast_index,
      count: 1 .. 32,
      found: boolean,
      ignore_relink_status: mmt$relink_page_status,
      ijle_p: ^jmt$initiated_job_list_entry,
      inhibit_io: boolean,
      mcount: integer,
      pass: reassign_pass,
      pfte_p: ^mmt$page_frame_table_entry,
      pfti: mmt$page_frame_index,
      pte_p: ^ost$page_table_entry,
      pti: integer,
      pti_offset: 0 .. 32,
      rcount: integer,
      sort_index: asid_list_index_type;


{ Reclaim unused ast entries.

    IF mmv$async_work.reclaim_astes THEN
      mmv$async_work.reclaim_astes := FALSE;
      mmp$asti (sva.asid, asti_that_cannot_be_freed);
      mmp$reclaim_ast_entries (asti_that_cannot_be_freed);
    IFEND;

{ Find the page table index that caused the page table full condition.

    #HASH_SVA (sva, pti, count, found);
    pti := pti - count + 1;
    IF pti < 0 THEN
      pti := pti + mmv$pt_length;
    IFEND;

    mmv$pt_full_trace.last_sva := sva;
    mmv$pt_full_trace.timestamp := #FREE_RUNNING_CLOCK (0);


{ Generate the list of ASIDs that can be changed to eliminate the page table full
{ condition. The list is sorted in order of 'easiest to change'.

    build_asid_list (sva.asid, pti, ^asidt, asidlmax);


{ Try to reassign an ASID until sucessful or reached end of list.

    trace (mmc$ap_ptf_called, 1);
    FOR sort_index := 1 TO asidlmax DO
      trace (mmc$ap_ptf_tried, 1);
      asidl := asidt [sort_index].index;
      reassign_asid (asidt [asidl].asid, asidt [asidl].aste_p, new_asid, new_asti, new_aste_p,
            pt_full_status);
      IF pt_full_status = mmc$pfs_asid_reassigned THEN
        IF asidt [asidl].asid = sva.asid THEN
          pt_full_status := mmc$pfs_input_asid_reassigned;
        IFEND;
        mmv$pt_full_trace.index := mmv$pt_full_trace.index + 1;
        IF mmv$pt_full_trace.index > 127 THEN
          mmv$pt_full_trace.index := 0;
        IFEND;
        mmv$pt_full_trace.asid [mmv$pt_full_trace.index].old := asidt [asidl].asid;
        mmv$pt_full_trace.asid [mmv$pt_full_trace.index].new := new_asid;
        mmv$pt_full_trace.changed_asid := mmv$pt_full_trace.changed_asid + 1;
        pass := asidt [asidl].sort_key DIV osc$max_page_frames;
        mmv$pt_full_trace.pass [pass] := mmv$pt_full_trace.pass [pass] + 1;
        RETURN; {<------}
      IFEND;
    FOREND;


{ Reassigning an ASID failed. Try to free pages in the part of the PT that has
{ the PT full condition by removing pages from job working sets of jobs that
{ are in the part of the page table that is full. This may write the page to disk.

    trace (mmc$ap_ptf_failed, 1);
    FOR pti_offset := 0 TO mmv$pt_search - 1 DO
      pte_p := ^mmv$pt_p^ [(pti)];
      pfti := (pte_p^.rma * 512) DIV osv$page_size;
      IF (pte_p^.pageid.asid <> 0) AND (pfti >= LOWERBOUND (mmv$pft_p^)) AND (pfti <= UPPERBOUND (mmv$pft_p^))
            THEN
        pfte_p := ^mmv$pft_p^ [pfti];
        IF ((pfte_p^.queue_id >= mmc$pq_shared_first) AND (pfte_p^.queue_id <= mmc$pq_shared_last)) OR
              (pfte_p^.queue_id = mmc$pq_job_working_set) THEN
          mmp$get_inhibit_io_status (pfte_p^.ijl_ordinal, TRUE {lock ajl} , inhibit_io, ijle_p);
          IF NOT inhibit_io THEN
            trace (mmc$ap_ptf_remove, 1);
            mmp$remove_page_from_jws (pfti, ijle_p, FALSE {= relink when Avail Mod Q max}, mcount, rcount,
                  ignore_relink_status);
            jmp$unlock_ajl (ijle_p);
          IFEND;
        IFEND;
      IFEND;
      pti := pti + 1;
      IF pti = mmv$pt_length THEN
        pti := 0;
      IFEND;
    FOREND;

    pt_full_status := mmc$pfs_failed;
    mmv$pt_full_trace.failed := mmv$pt_full_trace.failed + 1;


  PROCEND mmp$process_page_table_full;
?? TITLE := 'build_asid_list', EJECT ??
{ PURPOSE:
{   The purpose of this procedure is to examine the 32 entries in the page
{   table where the page table full occured.  A list of the unique ASIDs is
{   generated and sorted in order of easiest to reassign to hardest to reassign.
{
{ INPUT:
{   pt_full_asid: This parameter specifies the ASID that
{        hashed into the page table full area.
{   pt_full_index: This parameter is the index of the first
{        entry in the page table of the full area.
{   asid_list_p: This parameter points to the array for the
{        ASID list in the page table full area.
{
{ OUTPUT:
{   asid_list: (built in array pointed to by asid_list_p) List of ASID for potential
{        reassignment. List is sorted in order of least overhead of reassignment.
{   max_asid_list_index: index of the last entry in the ASID list.


{  Define type definition for the list of ASIDs returned by BUILD_ASID_LIST.

  TYPE
    asid_list_entry = record
      index: asid_list_index_type,
      sort_key: asid_list_key,
      asid: ost$asid,
      aste_p: ^mmt$active_segment_table_entry,
    recend,
    asid_list_index_type = 0 .. 33,
    asid_list_key = 0 .. osc$max_page_frames * 16,
    asid_list = array [1 .. 33] of asid_list_entry,
    reassign_pass = 1 .. 3;


  PROCEDURE build_asid_list
    (    pt_full_asid: ost$asid;
         pt_full_index: ost$page_table_index;
         asid_list_p: ^asid_list;
     VAR max_asid_list_index: asid_list_index_type);

    VAR
      asid: ost$asid,
      asid_list_index: asid_list_index_type,
      asid_list_index_max: asid_list_index_type,
      aste_p: ^mmt$active_segment_table_entry,
      done: boolean,
      ijle_p: ^jmt$initiated_job_list_entry,
      inhibit_reassign: boolean,
      pass: reassign_pass,
      pfti: mmt$page_frame_index,
      pti: integer,
      pti_offset: 0 .. 31,
      save_index: 0 .. 32;


{ If the image file is still being processed, discard all image pages. Pages that are still needed
{ will be faulted for again and new page table entries will be made.

    IF mmv$image_file.active THEN
      mmp$free_image_pages_mtr;
    IFEND;


{ Build an array of the ASIDs in the part of the PT that are involved in the
{ PT full condition. Ignore special ASIDs used by NOS or SSR. Skip free entries in the page table. If free
{ entries are encountered then either 1) PT full condition has cleared, or 2) more than one entry
{ is required in the page table. There is (currently) no way to distinguish between the cases. Since the
{ PT full condition may have been caused by a request that requires MULTIPLE entries to be made in the
{ page table, this routine cannot quit when a free entry is found.
{!NOTE: current algorithm does NOT force out pages in the AVAIL MODIFIED queue. This is ok until we start
{ to keep large numbers of pages in this queue. Then we should write pages unless IO in inhibited.
{ Note that FFFF cannot be reassigned (special significance to hardware) and the ASID of the page table
{ cannot be reassigned (requires mods to preset_memory routine.)

    asid_list_index_max := 0;
    pti := pt_full_index;

    FOR pti_offset := 0 TO mmv$pt_search - 1 DO

      IF pti_offset <> (mmv$pt_search - 1) THEN
        asid := mmv$pt_p^ [pti].pageid.asid;
        pfti := (mmv$pt_p^ [pti].rma * 512) DIV osv$page_size;
        IF (asid <> 0) AND (pfti >= LOWERBOUND (mmv$pft_p^)) AND (pfti <= UPPERBOUND (mmv$pft_p^)) THEN
          aste_p := mmv$pft_p^ [pfti].aste_p;
        ELSE
          asid := 0;
        IFEND;
      ELSE
        asid := pt_full_asid;
        mmp$aste_pointer (asid, aste_p);
        pfti := 0;
      IFEND;

      IF (asid = 0) OR (asid = 0ffff(16)) OR (asid = dsv$ssr_sdte.ste.asid) OR
            (asid = mtv$monitor_segment_table.st [0].ste.asid) OR (aste_p = NIL) THEN
        {Do nothing - cant change asid}
      ELSEIF (NOT jmp$ijl_block_valid (aste_p^.ijl_ordinal)) OR
            (jmv$ijl_p.block_p^ [aste_p^.ijl_ordinal.block_number].index_p^ [aste_p^.ijl_ordinal.block_index].
            entry_status = jmc$ies_entry_free) THEN
        IF pfti <> 0 THEN
          trace (mmc$ap_ba_freed_terj, 1);
          mmp$delete_pt_entry (pfti, TRUE);
          mmp$relink_page_frame (pfti, mmc$pq_free);
        IFEND;
      ELSE
        IF (aste_p^.sfid.residence = gfc$tr_system) AND (aste_p^.queue_id < mmc$pq_job_base) OR
              (pti_offset = (mmv$pt_search - 1)) THEN
          inhibit_reassign := FALSE;
        ELSE
          mmp$get_inhibit_io_status (aste_p^.ijl_ordinal, FALSE {lock ajl} , inhibit_reassign, ijle_p);
        IFEND;

        IF NOT inhibit_reassign THEN
          asid_list_index := 1;
          asid_list_p^ [asid_list_index_max + 1].asid := asid;
          WHILE (asid <> asid_list_p^ [asid_list_index].asid) DO
            asid_list_index := asid_list_index + 1;
          WHILEND;

          IF asid_list_index > asid_list_index_max THEN

            IF aste_p^.sfid.residence = gfc$tr_job THEN
              pass := 1;
            ELSEIF aste_p^.queue_id >= mmc$pq_job_base THEN
              pass := 2;
            ELSE
              pass := 3;
            IFEND;


            asid_list_index_max := asid_list_index_max + 1;
            asid_list_p^ [asid_list_index_max].sort_key := aste_p^.pages_in_memory + pass *
                  osc$max_page_frames;
            asid_list_p^ [asid_list_index_max].aste_p := aste_p;
            asid_list_p^ [asid_list_index_max].index := asid_list_index_max;
          IFEND;
        IFEND;
      IFEND;

      pti := pti + 1;
      IF pti = mmv$pt_length THEN
        pti := 0;
      IFEND;

    FOREND;


{ Sort the ASID list. List is sorted in the order of 'easiest to reassign'. See procedure
{ mmp$change_asid for more details on 'easy to reassign'.

    done := asid_list_index_max <= 1;
    WHILE NOT done DO
      done := TRUE;
      FOR asid_list_index := 1 TO asid_list_index_max - 1 DO
        IF asid_list_p^ [asid_list_p^ [asid_list_index].index].
              sort_key > asid_list_p^ [asid_list_p^ [asid_list_index + 1].index].sort_key THEN
          save_index := asid_list_p^ [asid_list_index].index;
          asid_list_p^ [asid_list_index].index := asid_list_p^ [asid_list_index + 1].index;
          asid_list_p^ [asid_list_index + 1].index := save_index;
          done := FALSE;
        IFEND;
      FOREND;
    WHILEND;

    max_asid_list_index := asid_list_index_max;

  PROCEND build_asid_list;
?? TITLE := 'reassign_asid - Used with page_table_full_handler', EJECT ??
{ PURPOSE:
{   This procedure is called by the PAGE_TABLE_FULL_HANDLER to reassign an ASID
{   That appears in the part of the page table that is full.
{
{ INPUT:
{    old_asid:    ASID to be reassigned
{    old_aste_p:  pointer to the AST table entry for the segment
{
{ OUTPUT:
{    new_asid:    newly assigned ASID
{    new_asti:    newly assigned AST index
{    new_aste_p:  newly assigned AST pointer
{    pt_full_status: status of reassign request
{       mmc$pfs_asid_reassigned
{       mmc$pfs_failed
{


  PROCEDURE reassign_asid
    (    old_asid: ost$asid;
         old_aste_p: ^mmt$active_segment_table_entry;
     VAR new_asid: ost$asid;
     VAR new_asti: mmt$ast_index;
     VAR new_aste_p: ^mmt$active_segment_table_entry;
     VAR pt_full_status: mmt$pt_full_status);

    VAR
      count: 1 .. 32,
      found: boolean,
      pti: integer,
      mpt_status: mmt$make_pt_entry_status,
      new_pte_p: ^ost$page_table_entry,
      new_sva: ost$system_virtual_address,
      temp_sva: ost$system_virtual_address,
      old_pte_p: ^ost$page_table_entry,
      old_sva: ost$system_virtual_address,
      pfte: mmt$page_frame_table_entry,
      pfte_p: ^mmt$page_frame_table_entry,
      pfti: mmt$page_frame_index,
      stop_pfti: mmt$page_frame_index,
      try_count: 0 .. 4;


{ Build the list of PFTIs for the segment being changed.

    old_sva.asid := old_asid;
    old_sva.offset := 0;
    mmp$initialize_find_next_pfti (old_sva, 7ffffff0(16), include_partial_pages, psc_all, old_aste_p, pfti);


{ Try several times to assign a new ASID for the segment.

    FOR try_count := 1 TO UPPERVALUE (try_count) DO
      mmp$assign_asid (new_asid, new_asti, new_aste_p);
      new_sva.asid := new_asid;
      new_aste_p^ := old_aste_p^;
      new_aste_p^.pages_in_memory := 0;
      mmp$reset_find_next_pfti (pfti);


{ Make page table entries using the new ASID for each page of the segment
{ that is currently in memory. Note that for a short time both the old and new PT entries
{ will exist. CAUTIONS:
{     - If PT full conditions occur during mmp$make_pt_entry, PT entries that are in the AVAIL
{       queue may be freed.
{     - Since mmp$make_pt_entry may freed PT entries, some of the PFTI in the pfti array
{       may already have been freed before this routine trys to change the ASID.
{ If a page table entry cannot be made and the page is in the AVAIL queue, the entry can be skipped.
{ Later in this procedure, the page will be deleted and linked to the FREE queue.
{ Note that the ^PFT entry passed to mmp$make_pt_entry is a dummy entry.
{
{ When this loop exits (either normally or abnormally, each page frame in the PFTI list  will
{ have 0, 1 or 2 page table entries for the page frame:
{    - both old and new (this is the state for ALL page frames except those in the AVAIL queue)
{    - old only. New was not made because PT full occurred.
{    - old only. New was made but was subsequently deleted by mmp$make_pt_entry due to PT full.
{    - new only. Old was deleted by PT full processing in mmp$make_pt_entry after new entry was made.
{    - no entries. Combination of previous 2 entries.

      trace (mmc$ap_rea_called, 1);

    /reassign_loop/
      BEGIN
        changing_asid := new_asid;
        changing_aste_p := new_aste_p;
        WHILE pfti <> 0 DO
          pfte_p := ^mmv$pft_p^ [pfti];
          IF pfte_p^.queue_id = mmc$pq_free THEN
            trace (mmc$ap_rea_in_free, 1);
            mmp$delete_last_pfti_from_array;
          ELSE
            trace (mmc$ap_rea_make_pt_entry, 1);
            new_sva.offset := pfte_p^.sva.offset;
            mmp$make_pt_entry (new_sva, pfti, new_aste_p, ^pfte, mpt_status);
            IF (mpt_status <> mmc$mpt_done) THEN
              IF (pfte_p^.queue_id <> mmc$pq_avail) THEN
                EXIT /reassign_loop/;
              IFEND;
              trace (mmc$ap_rea_mpte_fail, 1);
            ELSE
              old_pte_p := ^mmv$pt_p^ [pfte_p^.pti];
              new_pte_p := ^mmv$pt_p^ [pfte.pti];
              new_pte_p^.u := FALSE;
              new_pte_p^.v := old_pte_p^.v;
            IFEND;
          IFEND;
          mmp$find_next_pfti (pfti);
        WHILEND;
        changing_asid := 0;




{ Page table entries have been made for all pages of the segment. Now locate
{ all segment table entries that have the ASID and change it to the new ASID.

        mmp$change_asid (old_aste_p, old_asid, new_asid, new_asti);


{ If the ASID being changed belongs to mainframe wired, save the new ASID. This is required in case
{ the system crashes while the next couple of blocks of CYBIL statements are being executed. System
{ recovery
{ must be able to locate PT entries that belong to mainframe wired.

        IF old_asid = mmv$mf_wired_asid_p^.current THEN
          mmv$mf_wired_asid_p^.new := new_asid;
        IFEND;


{ Now delete the PT entries that have the old ASID and update the PFT with the
{ new segment info. The correct value of the 'used' and 'modified' bits are captured
{ here from the old entries and copied to the new PT entries.

        trace (mmc$ap_rea_ok, 1);
        mmp$reset_find_next_pfti (pfti);
        WHILE pfti <> 0 DO
          pfte_p := ^mmv$pft_p^ [pfti];
          IF pfte_p^.queue_id <> mmc$pq_free THEN
            temp_sva.offset := pfte_p^.sva.offset;
            temp_sva.asid := new_asid;
            #HASH_SVA (temp_sva, pti, count, found);
            IF NOT found THEN

{ Set changing_asid to a non-zero value to indicate that page table full processing is occurring.
{ This page frame is in the available queue and a new page table entry could not be made, so
{ delete_pt_entry will unlink the page from the segment.  Because page frames that had a new page
{ table entry made for them will not be unlinked from the segment, the debug code in unlink page
{ from segment must not be executed.  (If this is the last page in the segment, the ast.pages_in_memory
{ will be zero, but the ast.pft_link will still have the links to be copied to the new ast entry later.)

              changing_asid := 1;
              mmp$delete_pt_entry (pfti, TRUE);
              changing_asid := 0;
              pfte_p^.sva.asid := new_asid;
              trace (mmc$ap_rea_ok1, 1);
              mmp$relink_page_frame (pfti, mmc$pq_free);
            ELSE
              mmp$delete_pt_entry (pfti, FALSE);
              pfte_p^.sva.asid := new_asid;
              trace (mmc$ap_rea_ok2, 1);
              IF mmv$pt_p^ [pfte_p^.pti].m THEN
                mmv$pt_p^ [pti].m := TRUE;
              IFEND;
              IF mmv$pt_p^ [pfte_p^.pti].u THEN
                mmv$pt_p^ [pti].u := TRUE;
              IFEND;
              pfte_p^.pti := pti;
              pfte_p^.aste_p := new_aste_p;
            IFEND;
          ELSE
            trace (mmc$ap_rea_ok3, 1);
            pfte := pfte_p^;
            pfte_p^.sva.asid := new_asid;
            #HASH_SVA (pfte_p^.sva, pti, count, found);
            IF found THEN
              trace (mmc$ap_rea_ok4, 1);
              pfte_p^.pti := pti;
              pfte_p^.aste_p := new_aste_p;
              mmp$delete_pt_entry (pfti, FALSE);
            IFEND;
            pfte_p^ := pfte;
          IFEND;
          mmp$find_next_pfti (pfti);
        WHILEND;

{ Copy the ast.pft_link information again; delete_pt_entry may have changed the links.

        new_aste_p^.pft_link := old_aste_p^.pft_link;
        old_aste_p^.pft_link.fwd := 0;
        old_aste_p^.pft_link.bkw := 0;

        mmp$free_asid (old_asid, old_aste_p);


{ If the ASID that was changed belonged to mainframe wired, update the
{ mainframe-wired-asid record.

        IF old_asid = mmv$mf_wired_asid_p^.current THEN
          mmv$mf_wired_asid_p^.current := mmv$mf_wired_asid_p^.new;
          mmv$mf_wired_asid_p^.new := 0;
        IFEND;

        pt_full_status := mmc$pfs_asid_reassigned;
        RETURN; { <-------}

      END /reassign_loop/;
      changing_asid := 0;


{ Control gets here only if a page table entry could not be made for the new ASID.
{ Delete all PT entries made with the new ASID and try again. NOTE: If an entry cannot be found,
{ then it must have been in the AVAIL queue and was deleted by mmp$make_pt_entry as a
{ result of page table full processing.

      stop_pfti := pfti;
      mmp$reset_find_next_pfti (pfti);
      trace (mmc$ap_rea_fail, 1);
      WHILE pfti <> stop_pfti DO
        trace (mmc$ap_rea_fail1, 1);
        pfte_p := ^mmv$pft_p^ [pfti];
        new_sva.offset := pfte_p^.sva.offset;
        #HASH_SVA (new_sva, pti, count, found);
        IF found THEN
          trace (mmc$ap_rea_fail2, 1);
          pfte := pfte_p^;
          pfte_p^.pti := pti;
          pfte_p^.sva.asid := new_asid;
          pfte_p^.aste_p := new_aste_p;
          mmp$delete_pt_entry (pfti, FALSE);
          pfte_p^ := pfte;
        IFEND;
        mmp$find_next_pfti (pfti);
      WHILEND;
      new_aste_p^.pft_link.fwd := 0;
      new_aste_p^.pft_link.bkw := 0;
      mmp$free_asid (new_asid, new_aste_p);
    FOREND;


{ Control gets here only if all attempts to reassign the ASID fail. Return
{ bad status and exit.

    trace (mmc$ap_rea_quit, 1);
    pt_full_status := mmc$pfs_failed;

  PROCEND reassign_asid;

MODEND mmm$asid_page_table_manager
