?? RIGHT := 110 ??
?? NEWTITLE := 'NOS/VE Deadstart : Deadstart IO' ??
MODULE dsm$deadstart_io;

{ PURPOSE:
{   This module contains all of the procedures needed to read data from the deadstart device.
{ NOTES:
{   This module resides in both the BOOT and in the SYSTEM CORE.

?? NEWTITLE := 'Global Declarations Referenced by This Module', EJECT ??
?? PUSH (LISTEXT := ON) ??
*copyc dmc$deadstart_file_alloc_size
*copyc dmt$deadstart_label_files
*copyc dmt$mau_list
*copyc dse$deadstart_io_errors
*copyc dst$deadstart_file_identifier
*copyc dst$header_information
*copyc fst$ansi_hdr1_label
*copyc fst$ansi_hdr2_label
*copyc fst$ansi_vol1_label
?? POP ??
*copyc cmp$get_logical_unit_number
*copyc cmp$get_unit_type
*copyc dmp$get_physical_attributes
*copyc dmp$locate_volume_label
*copyc dsp$fetch_boot_data
*copyc dsp$save_boot_data_pointer
*copyc iop$forspace_tape_to_tapemark
*copyc iop$free_boot_tape_tables
*copyc iop$initialize_tape_ud
*copyc iop$mass_storage_io
*copyc iop$read_tape
*copyc iop$rewind_tape
*copyc iop$tape_request_status
*copyc iop$unload_tape
*copyc osp$append_status_parameter
*copyc osp$set_status_abnormal
*copyc osp$system_error
*copyc pmp$delay
*copyc pmp$zero_out_table
?? EJECT ??
*copyc cmv$system_device_data
*copyc osv$boot
*copyc osv$boot_is_executing
*copyc osv$mainframe_wired_heap
?? TITLE := 'Global Declarations Declared by This Module', EJECT ??
  CONST
    max_disk_data_length = dmc$deadstart_file_alloc_size,
    max_mau_list_size = 20000000 DIV dmc$deadstart_file_alloc_size,

  { Reel to reel tape constants.

    max_tape_blocks_reel = 10,
    max_tape_data_length_reel = max_tape_blocks_reel * ioc$max_tape_not_long_blk_lgth,

  { Cartridge tape constants.

    max_tape_blocks_cart = 1,
    max_tape_data_length_cart = max_tape_blocks_cart * ioc$cart_tape_default_maxbl;

  TYPE
    current_blocks = 1 .. (max_tape_blocks_reel + 1),

    deadstart_disk_information = RECORD
      device_address: dmt$ms_logical_device_address,
      current_address: 0 .. (dmc$max_mau_address + 1),
      mau_count: dmt$mau_count,
      buffer_amount: 0 .. max_disk_data_length,
      file_size: integer,
    RECEND,

    deadstart_header_information = RECORD
      block_type: string (2),
      record_type: string (1),
    RECEND,

    deadstart_io_data = RECORD
      unit_class: cmt$unit_class,
      cm_unit_type: cmt$unit_type,
      data_end_mark_reached: boolean,
      data_end_mark_read: boolean,
      header_information: deadstart_header_information,
      disk: deadstart_disk_information,
      tape: deadstart_tape_information,
    RECEND,

    deadstart_label_id = string (4),

    deadstart_tape_information = RECORD
      maxbl: 0 .. ioc$cart_tape_default_maxbl,
      maximum_blocks: 0 .. max_tape_blocks_reel,
      blocks_read: 0 .. max_tape_blocks_reel,
      current_block: current_blocks,
      block_size_already_read: 0 .. ioc$cart_tape_default_maxbl,
    RECEND,

    { Note - The following type must be aligned so that all the tape transfer count buffers
    { reside in the same memory page.

    deadstart_tape_transfer_counts = RECORD
      buffers: ALIGNED [0 MOD 256] ARRAY [1 .. *] OF iot$tape_transfer_count,
    RECEND;
?? EJECT ??
  VAR
    dsv$unload_deadstart_tape: [XDCL] boolean := TRUE,
    osv$deadstart_device_lun: [XDCL] iot$logical_unit,

    io_buffer_seq_p: ^SEQ ( * ) := NIL,
    io_data: deadstart_io_data,
    label_seq_p: ^SEQ ( * ) := NIL,
    mau_list_p: ^dmt$mau_address_list := NIL,
    tape_data: iot$read_tape_description,
    tape_transfer_counts_p: ^deadstart_tape_transfer_counts := NIL;
?? TITLE := 'check_tape_status', EJECT ??

{ PURPOSE:
{   This procedure checks the status of the tape request.
{   If the input parameter wait_for_completion is FALSE, the tape_io_status
{   has already been obtained and is an input to this procedure

  PROCEDURE check_tape_status
    (    io_id: iot$io_id;
         wait_for_completion: boolean;
     VAR tape_io_status: iot$tape_io_status;   {input if wait_for_completion = False
     VAR status: ost$status);

    VAR
      dummy_sfid: gft$system_file_identifier,
      tape_condition_code: ost$status_condition_code,
      tape_error_text: string (80);

    status.normal := TRUE;

  /wait_for_status/
    WHILE TRUE DO
      IF wait_for_completion THEN
        iop$tape_request_status (dummy_sfid, io_id, {wait=} FALSE, tape_io_status, status);
        IF NOT status.normal THEN
          RETURN;
        IFEND;
        IF NOT tape_io_status.io_complete THEN
          pmp$delay (100, status);
          CYCLE /wait_for_status/;
        IFEND;
      IFEND;

      IF NOT tape_io_status.unit_ready THEN
        osp$set_status_abnormal (dsc$display_processor_id, dse$tape_io_error,
              'The tape unit is not ready.', status);
        RETURN;
      IFEND;

      tape_condition_code := dse$tape_io_error;
      tape_error_text := ' ';

      IF NOT tape_io_status.normal_completion THEN
        CASE tape_io_status.completion_code OF
        = ioc$tapemark_read =
          io_data.data_end_mark_read := TRUE;
        = ioc$load_point =
          tape_error_text := 'Tape IO error - load_point.';
        = ioc$load_point_block_count_ne_0 =
          tape_error_text := 'Tape IO error - load point block count ne 0.';
        = ioc$no_write_ring =
          tape_error_text := 'Tape IO error - no write ring.';
        = ioc$not_capable_of_density =
          tape_error_text := 'Tape IO error - not capable of density.';
        = ioc$blank_tape =
          tape_error_text := 'Tape IO error - blank tape.';
        = ioc$erase_limit_exceeded =
          tape_error_text := 'Tape IO error - erase limit exceeded.';
        = ioc$system_software_failure =
          tape_error_text := 'Tape IO error - system software failure.';
        = ioc$read_past_phys_eot =
          tape_error_text := 'Tape IO error - attempted read past physical EOT.';
        = ioc$indeterminate =
          tape_error_text := 'Tape IO error - indeterminate.';
        = ioc$input_channel_parity =
          tape_error_text := 'Tape IO error - input channel parity.';
        = ioc$user_own_recovery =
          tape_error_text := 'Tape IO error - user own recovery.';
        = ioc$output_channel_parity =
          tape_error_text := 'Tape IO error - output channel parity.';
        = ioc$controller_failure =
          tape_error_text := 'Tape IO error - controller failure.';
        = ioc$unit_failure =
          tape_error_text := 'Tape IO error - unit failure.';
        = ioc$function_timeout =
          tape_error_text := 'Tape IO error - functIOn timeout.';
        = ioc$tape_medium_failure =
          tape_error_text := 'Tape IO error - tape medium failure.';
        = ioc$unit_reserved =
          tape_error_text := 'Tape IO error - unit reserved.';
        = ioc$iou_output_parity =
          tape_error_text := 'Tape IO error - IOu output parity.';
        = ioc$indeterminate_output_parity =
          tape_error_text := 'Tape IO error - indeterminate output parity.';
        = ioc$unable_to_set_agc =
          tape_error_text := 'Tape IO error - unable to set AGC.';
        = ioc$alert_condition_encountered =
          tape_condition_code := dse$damaged_deadstart_file;
          tape_error_text := 'The deadstart file is damaged or built incorrectly';
        ELSE
          osp$system_error ('Unknown tape failure', #LOC (tape_io_status));
        CASEND;

      ELSE {normal completion}
        IF tape_io_status.end_of_tape THEN
          tape_error_text := 'Tape IO error - end of tape.';
        IFEND;
        IF tape_io_status.position_uncertain THEN
          tape_error_text := 'Tape IO error - position uncertain.';
        IFEND;
      IFEND;

      IF tape_error_text <> ' ' THEN
        osp$set_status_abnormal (dsc$display_processor_id, tape_condition_code, tape_error_text, status);
      IFEND;
      RETURN;

    WHILEND /wait_for_status/;

  PROCEND check_tape_status;
?? TITLE := 'convert_string_to_file_size', EJECT ??

{ PURPOSE:
{   This procedure converts the file size string that is found in the HDR2 label to an integer value.

  PROCEDURE convert_string_to_file_size
    (    hdr2_label: fst$ansi_hdr2_label);

    VAR
      actual_integer: 0 .. 0ff(16),
      file_size: integer,
      file_size_string: string (8),
      integer_char: 0 .. 0ff(16),
      multiplier: integer,
      string_index: 1 .. 8;

    file_size_string := hdr2_label.block_length;
    file_size_string ((#SIZE (hdr2_label.block_length) + 1), *) := hdr2_label.ve_block_length_ext;
    file_size := 0;
    multiplier := 1;
    FOR string_index := 8 DOWNTO 1 DO
      integer_char := $INTEGER (file_size_string (string_index));
      IF (integer_char >= $INTEGER ('0')) AND (integer_char <= $INTEGER ('9')) THEN
        actual_integer := integer_char - $INTEGER ('0');
      ELSEIF (integer_char >= $INTEGER ('A')) AND (integer_char <= $INTEGER ('F')) THEN
        actual_integer := integer_char - $INTEGER ('7');
      ELSE
        osp$system_error ('ERROR: invalid file size on disk deadstart file.', NIL);
      IFEND;
      file_size := file_size + (actual_integer * multiplier);
      multiplier := multiplier * 10(16);
    FOREND;

    io_data.disk.file_size := file_size;

  PROCEND convert_string_to_file_size;
?? TITLE := 'position_deadstart_file', EJECT ??

{ PURPOSE:
{   This procedure reads the VOL1 label and the data up to the first "data end" mark.  Certain information
{   is checked in the VOL1, HDR1, and HDR2 labels that exist before this first "data end" mark.  This
{   information determines whether the deadstart file is valid.

  PROCEDURE position_deadstart_file
    (VAR status: ost$status);

    VAR
      hdr1_label_p: ^fst$ansi_hdr1_label,
      hdr2_label_p: ^fst$ansi_hdr2_label,
      label_found: boolean,
      label_id: deadstart_label_id,
      monitor_image_found: boolean;

    status.normal := TRUE;

    IF io_data.unit_class = cmc$mass_storage_unit THEN
      io_data.disk.file_size := #SIZE (fst$ansi_hdr1_label) * 3;
    IFEND;

    { Read the VOL1 label.

    read_label (label_seq_p, label_id, label_found);
    IF NOT label_found OR (label_id <> 'VOL1') THEN
      osp$set_status_abnormal (dsc$display_processor_id, dse$incorrect_deadstart_file,
            'Incorrect deadstart file: No VOL1 label', status);
      RETURN;
    IFEND;

    { Skip over the other possible VOLN labels and read the HDR1 label.

    REPEAT
      read_label (label_seq_p, label_id, label_found);
    UNTIL NOT label_found OR (label_id = 'HDR1');
    IF NOT label_found THEN
      osp$set_status_abnormal (dsc$display_processor_id, dse$incorrect_deadstart_file,
            'Incorrect deadstart file: No HDR1 label', status);
      RETURN;
    IFEND;

    { Skip over the CIP files on the deadstart tape.  Read until the MONITOR_IMAGE file is found.

    WHILE TRUE DO
      NEXT hdr1_label_p IN label_seq_p;
      monitor_image_found := (hdr1_label_p^.file_identifier = 'MONITOR_IMAGE');

      { Read the HDR2 label.

      read_label (label_seq_p, label_id, label_found);
      IF NOT label_found OR (label_id <> 'HDR2') THEN
        osp$set_status_abnormal (dsc$display_processor_id, dse$incorrect_deadstart_file,
              'Incorrect deadstart file: No HDR2 label', status);
        RETURN;
      IFEND;

      { Retrieve the preceeding file size from the HDR2 label for disk deadstarts.

      NEXT hdr2_label_p IN label_seq_p;
      io_data.header_information.block_type := hdr2_label_p^.ve_block_type;
      io_data.header_information.record_type := hdr2_label_p^.ve_record_type;

      { Skip over any more HDRN labels.

      skip_to_next_data_end;

      IF io_data.unit_class = cmc$mass_storage_unit THEN
        convert_string_to_file_size (hdr2_label_p^);
      IFEND;

      IF monitor_image_found THEN
        RETURN;
      IFEND;

      { Skip over the CIP file.

      skip_to_next_data_end;

      { Skip over the EOFN records.

      skip_to_next_data_end;

      { Read the next HDR1 label.

      read_label (label_seq_p, label_id, label_found);
      IF NOT label_found THEN
        osp$set_status_abnormal (dsc$display_processor_id, dse$incorrect_deadstart_file,
              'Incorrect deadstart file: No HDR1 label', status);
        RETURN;
      IFEND;
    WHILEND;

  PROCEND position_deadstart_file;
?? TITLE := 'read_data', EJECT ??

{ PURPOSE:
{   This procedure reads data from the deadstart file into an IO buffer.  The IO buffer is a set size.  This
{   procedure is called to fill the IO buffer.  The deadstart file data is read until the IO buffer is full
{   or until the "data end" mark is reached.  The "data end" mark is a tape mark on a tape or the end of a
{   file on the disk.  The end of a file on the disk can be found by the size of the file which is stored in
{   the HDR2 label on the disk.  When all of the data has been read, according to this size in the label,
{   then the "data end" mark has been found.  This procedure maintains the file size.

  PROCEDURE read_data;

    VAR
      disk_completion_status_p: ^iot$completion_status,
      dummy_sfid: gft$system_file_identifier,
      io_id: iot$io_id,
      status: ost$status,
      tape_io_status: iot$tape_io_status;

    CASE io_data.unit_class OF
    = cmc$magnetic_tape_unit =
      io_data.data_end_mark_read := FALSE;
      iop$read_tape (dummy_sfid, FALSE, io_data.tape.maxbl, ^tape_data,
            io_data.tape.maximum_blocks, io_id, status);
      IF NOT status.normal THEN
        osp$system_error ('Tape IO error', ^status);
      IFEND;

      check_tape_status (io_id, TRUE, tape_io_status, status);
      IF NOT status.normal THEN
        osp$system_error ('Tape IO error', ^status);
      IFEND;

      reset_tape_data_area;
      io_data.tape.current_block := 1;
      io_data.tape.blocks_read := io_data.tape.maximum_blocks - tape_io_status.residual_block_count;
      io_data.tape.block_size_already_read := 0;

    = cmc$mass_storage_unit =
      IF io_data.disk.buffer_amount <= 0 THEN
        IF io_data.disk.current_address > io_data.disk.mau_count THEN
          osp$system_error ('Read past end of deadstart file', NIL);
        IFEND;
        PUSH disk_completion_status_p;
        RESET io_buffer_seq_p;
        io_data.disk.device_address.allocation_unit_mau_address := mau_list_p^ [io_data.disk.current_address];
        iop$mass_storage_io (io_buffer_seq_p, max_disk_data_length, ioc$read_mass_storage,
              io_data.disk.device_address, TRUE, disk_completion_status_p, status);
        IF NOT status.normal THEN
          osp$system_error ('Disk read failure', ^status);
        IFEND;
        RESET io_buffer_seq_p;
        io_data.disk.current_address := io_data.disk.current_address + 1;
        io_data.disk.buffer_amount := max_disk_data_length;
      IFEND;
      io_data.data_end_mark_read := (io_data.disk.file_size <= io_data.disk.buffer_amount);

    ELSE
      osp$system_error ('The deadstart device is unknown', NIL);
    CASEND;

  PROCEND read_data;
?? TITLE := 'read_label', EJECT ??

{ PURPOSE:
{   This procedure is called to read a label from the deadstart device.

  PROCEDURE read_label
    (VAR label_seq_p: ^SEQ ( * );
     VAR label_id: deadstart_label_id;
     VAR label_found: boolean);

    VAR
      data_size_read: integer,
      label_p: ^fst$ansi_hdr1_label;

    { Read the label.

    RESET label_seq_p;
    dsp$read_deadstart_device (#SIZE (fst$ansi_hdr1_label), label_seq_p, data_size_read);
    IF data_size_read < #SIZE (fst$ansi_hdr1_label) THEN
      label_found := FALSE;
      RETURN;
    IFEND;

    { Retrive the label id (ie. VOL1, HDR1 or HDR2) from the label.

    RESET label_seq_p;
    NEXT label_p IN label_seq_p;
    label_id (1, 3) := label_p^.label_identifier;
    label_id (4) := label_p^.label_number;

    RESET label_seq_p;
    label_found := TRUE;

  PROCEND read_label;
?? TITLE := 'reset_tape_data_area', EJECT ??

{ PURPOSE:
{   This procedure resets the tape data area.  This must be done initially and after every
{   call to IOP$READ_TAPE.  The procedure iop$read_tape changes the values in the tape data variable.

  PROCEDURE reset_tape_data_area;

    VAR
      block_index: 1 .. max_tape_blocks_reel,
      data_seq_p: ^SEQ ( * );

    RESET io_buffer_seq_p;
    FOR block_index := 1 TO io_data.tape.maximum_blocks DO
      tape_data [block_index].block_transfer_length := ^tape_transfer_counts_p^.buffers [block_index];
      IF io_data.cm_unit_type = cmc$mt5682_1x THEN
        NEXT data_seq_p: [[REP ioc$cart_tape_default_maxbl OF cell]] IN io_buffer_seq_p;
      ELSE
        NEXT data_seq_p: [[REP ioc$max_tape_not_long_blk_lgth OF cell]] IN io_buffer_seq_p;
      IFEND;
      tape_data [block_index].buffer_area := ^data_seq_p^;
    FOREND;

  PROCEND reset_tape_data_area;
?? TITLE := 'skip_to_next_data_end', EJECT ??

{ PURPOSE:
{   This procedure moves the tape or disk file forward past the next data end mark.

  PROCEDURE skip_to_next_data_end;

    VAR
      data_size: integer,
      dummy_io_id: iot$io_id,
      dummy_sfid: gft$system_file_identifier,
      tape_io_status: iot$tape_io_status,
      status: ost$status,
      skip_data_p: ^SEQ ( * );

    IF NOT io_data.data_end_mark_reached THEN

      CASE io_data.unit_class OF
      = cmc$magnetic_tape_unit =

        IF NOT io_data.data_end_mark_read THEN
          iop$forspace_tape_to_tapemark (dummy_sfid, tape_io_status, status);
          IF NOT status.normal THEN
            osp$system_error ('Skip tapemark failed.', ^status);
          IFEND;
          check_tape_status (dummy_io_id, FALSE, tape_io_status, status);
          IF NOT status.normal THEN
            osp$system_error ('Skip tapemark I/O error.', ^status);
          IFEND;
        IFEND;
        io_data.tape.current_block := io_data.tape.maximum_blocks + 1;

      = cmc$mass_storage_unit =

        WHILE io_data.disk.file_size > 0 DO
          IF io_data.disk.buffer_amount <= 0 THEN
            read_data;
          IFEND;
          IF io_data.disk.buffer_amount < io_data.disk.file_size THEN
            data_size := io_data.disk.buffer_amount;
          ELSE
            data_size := io_data.disk.file_size;
          IFEND;
          NEXT skip_data_p: [[REP data_size OF cell]] IN io_buffer_seq_p;
          io_data.disk.buffer_amount := io_data.disk.buffer_amount - data_size;
          io_data.disk.file_size := io_data.disk.file_size - data_size;
        WHILEND

      ELSE
        osp$system_error ('The deadstart device is unknown.', NIL);
      CASEND;
    IFEND;

    io_data.data_end_mark_reached := FALSE;
    io_data.data_end_mark_read := FALSE;

  PROCEND skip_to_next_data_end;
?? TITLE := 'dsp$cleanup_deadstart_io', EJECT ??

{ PURPOSE:
{   This procedure is called when the deadstart IO is finished.  It rewinds the tape and
{   frees all the allocated space.

  PROCEDURE [XDCL, #GATE] dsp$cleanup_deadstart_io
    (VAR status: ost$status);

    VAR
      detachment_options: fmt$detachment_options,
      dummy_sfid: gft$system_file_identifier,
      io_id: iot$io_id,
      tape_io_status: iot$tape_io_status;

    status.normal := TRUE;
    IF io_data.unit_class = cmc$magnetic_tape_unit THEN
      IF osv$boot_is_executing OR (NOT dsv$unload_deadstart_tape) THEN
        iop$rewind_tape (dummy_sfid, io_id, status);
        IF status.normal THEN
          check_tape_status (io_id, TRUE, tape_io_status, status);
        IFEND;
        IF NOT osv$boot_is_executing THEN
          iop$free_boot_tape_tables;
        IFEND;
      ELSE
        detachment_options.device_class := rmc$magnetic_tape_device;
        detachment_options.physical_unload := TRUE;
        iop$unload_tape (dummy_sfid, detachment_options, io_id, status);
        IF status.normal THEN
          check_tape_status (io_id, TRUE, tape_io_status, status);
        IFEND;
      IFEND;
    IFEND;

    IF tape_transfer_counts_p <> NIL THEN
      FREE tape_transfer_counts_p IN osv$mainframe_wired_heap^;
    IFEND;

    IF mau_list_p <> NIL THEN
      FREE mau_list_p IN osv$mainframe_wired_heap^;
    IFEND;

    IF io_buffer_seq_p <> NIL THEN
      FREE io_buffer_seq_p IN osv$mainframe_wired_heap^;
    IFEND;

    IF label_seq_p <> NIL THEN
      FREE label_seq_p IN osv$mainframe_wired_heap^;
    IFEND;

    osv$boot := osv$boot_is_executing;

  PROCEND dsp$cleanup_deadstart_io;
?? TITLE := 'dsp$fetch_mau_list', EJECT ??

{ PURPOSE:
{   This procedure fetches the MAU list of the deadstart file on the system device.

  PROCEDURE [XDCL] dsp$fetch_mau_list
    (    only_perform_allocates: boolean;
         label_file: dmt$deadstart_label_files;
     VAR mau_list_p: ^dmt$mau_address_list;
     VAR mau_count: dmt$mau_count;
     VAR transfer_size: integer;
     VAR status: ost$status);

    VAR
      completion_status_p: ^iot$completion_status,
      device_address: dmt$ms_logical_device_address,
      full_mau_list_p: ^dmt$mau_list,
      index: dmt$mau_count,
      label_area_p: ^ARRAY [1 .. * ] OF cell,
      label_found: boolean,
      label_header_p: ^dmt$volume_label_header,
      label_p: ^dmt$ms_volume_label,
      mau_address: dmt$mau_address_entry,
      mau_list_index: dmt$mau_count,
      physical_attributes_p: ^dmt$physical_device_attributes,
      required_io_size: integer;

    status.normal := TRUE;

    { Retrieve the physical attributes of the disk device.

    PUSH physical_attributes_p: [1 .. 4];
    physical_attributes_p^ [1].keyword := dmc$bytes_per_mau;
    physical_attributes_p^ [2].keyword := dmc$maus_per_cylinder;
    physical_attributes_p^ [3].keyword := dmc$maus_per_dau;
    physical_attributes_p^ [4].keyword := dmc$cylinders_per_device;
    dmp$get_physical_attributes (cmv$system_device_data [cmc$sdt_disk_device].unit_id, physical_attributes_p,
          status);
    IF NOT status.normal THEN
      osp$system_error ('Disk initialization failed', ^status);
    IFEND;

    { Allocate all of the necessary space to account for memory size.

    PUSH completion_status_p;
    PUSH label_p: [[REP #SIZE (dmt$volume_label_header) OF cell]];
    PUSH label_area_p: [1 .. dmc$deadstart_file_alloc_size];
    pmp$zero_out_table (label_area_p, #SIZE (label_area_p^));
    ALLOCATE mau_list_p: [1 .. max_mau_list_size] IN osv$mainframe_wired_heap^;
    pmp$zero_out_table (mau_list_p, #SIZE (mau_list_p^));

    IF only_perform_allocates THEN
      RETURN;
    IFEND;

    { Setup the disk IO.

    dsp$retrieve_device_address (dmc$deadstart_file_alloc_size, device_address);

    { Read the label from the disk.

    dmp$locate_volume_label (device_address.logical_unit_number, physical_attributes_p,
          label_p^, status);
    IF NOT status.normal THEN
      CASE status.condition OF
      = ioe$unit_disabled =
        osp$set_status_abnormal (dsc$display_processor_id, dse$disk_io_error,
              'Volume label not found on system device - device was downed due to IO error.', status);
        RETURN;

      = dme$no_ms_label_type =
        osp$set_status_abnormal (dsc$display_processor_id, dse$disk_io_error,
              'Volume label not found on system device.', status);
        RETURN;

      = ioe$unrecovered_disk_error =
        osp$append_status_parameter (osc$status_parameter_delimiter, ' in trying to read the label', status);
        RETURN;

      ELSE
        RETURN;
      CASEND;
    IFEND;

    RESET label_p;
    NEXT label_header_p IN label_p;

    { Retrieve the mau address from the label.

    CASE label_file OF
    = dmc$dlf_primary_entry =
      mau_address := label_header_p^.primary_deadstart_file;
    = dmc$dlf_secondary_entry =
      mau_address := label_header_p^.secondary_deadstart_file;
    = dmc$dlf_image_entry =
      mau_address := label_header_p^.image_file;
    = dmc$dlf_spare_entry =
      mau_address := label_header_p^.spare_file;
    ELSE
    CASEND;
    IF (mau_address <= 0) OR (mau_address > (physical_attributes_p^ [2].maus_per_cylinder *
          physical_attributes_p^ [4].cylinders_per_device)) THEN
      IF label_file = dmc$dlf_image_entry THEN
        osp$set_status_abnormal (dsc$display_processor_id, dse$disk_io_error,
              'Image file not found on system device.', status);
      ELSE
        osp$set_status_abnormal (dsc$display_processor_id, dse$disk_io_error,
              'Deadstart file not installed.', status);
      IFEND;
      RETURN;
    IFEND;

    { Read the first mau list block as a small block.

    device_address.allocation_unit_mau_address := mau_address;
    iop$mass_storage_io (label_area_p, dmc$deadstart_file_alloc_size, ioc$read_mass_storage,
          device_address, TRUE, completion_status_p, status);
    IF NOT status.normal THEN
      osp$system_error ('Disk read failure', ^status);
    IFEND;

    { Retrieve the mau list plus mau list header from the label area.

    full_mau_list_p := #LOC (label_area_p^);
    IF (full_mau_list_p^.header.valid_data <> 1) OR
          (full_mau_list_p^.header.first_mau_of_maufile <> mau_address) THEN
      IF label_file = dmc$dlf_image_entry THEN
        osp$set_status_abnormal (dsc$display_processor_id, dse$disk_io_error,
              'Image file not found on system device.', status);
      ELSE
        osp$set_status_abnormal (dsc$display_processor_id, dse$disk_io_error,
              'Deadstart file not installed.', status);
      IFEND;
      RETURN;
    IFEND;
    transfer_size := full_mau_list_p^.header.block_size;
    mau_count := full_mau_list_p^.header.total_mau_addresses;

    { Read only the required amount to save memory space.

    required_io_size := transfer_size;
    IF ((mau_count * 8) + #SIZE (dmt$mau_list_header)) < required_io_size THEN
      required_io_size := ((mau_count * 8) + #SIZE (dmt$mau_list_header));
      IF (required_io_size MOD physical_attributes_p^ [1].bytes_per_mau) <> 0 THEN
        required_io_size := required_io_size + (physical_attributes_p^ [1].bytes_per_mau -
              (required_io_size MOD physical_attributes_p^ [1].bytes_per_mau));
      IFEND;
    IFEND;

    { Re-allocate IO area to the required size.

    PUSH label_area_p: [1 .. required_io_size];
    full_mau_list_p := #LOC (label_area_p^);
    pmp$zero_out_table (label_area_p, required_io_size);
    dsp$retrieve_device_address (required_io_size, device_address);

    { Retrieve the MAU list from the disk.

    IF mau_count > max_mau_list_size THEN
      FREE mau_list_p IN osv$mainframe_wired_heap^;
      ALLOCATE mau_list_p: [1 .. mau_count] IN osv$mainframe_wired_heap^;
      pmp$zero_out_table (mau_list_p, #SIZE (mau_list_p^));
    IFEND;
    mau_list_index := 0;
    WHILE mau_address <> 0 DO
      device_address.allocation_unit_mau_address := mau_address;
      iop$mass_storage_io (label_area_p, required_io_size, ioc$read_mass_storage,
            device_address, TRUE, completion_status_p, status);
      IF NOT status.normal THEN
        osp$system_error ('Disk read failure', ^status);
      IFEND;
      FOR index := 1 TO (required_io_size - #SIZE (dmt$mau_list_header)) DIV 8 DO
        IF mau_list_index < mau_count THEN
          mau_list_index := mau_list_index + 1;
          mau_list_p^ [mau_list_index] := full_mau_list_p^.mau_addresses [index];
        IFEND;
      FOREND;
      mau_address := full_mau_list_p^.header.next_mau_list_address;
    WHILEND;

  PROCEND dsp$fetch_mau_list;
?? TITLE := 'dsp$initialize_io', EJECT ??

{ PURPOSE:
{   This procedure initializes the tape for deadstart.

  PROCEDURE [XDCL, #GATE] dsp$initialize_io
    (    element_name: cmt$element_name;
     VAR status: ost$status);

    VAR
      dummy_sfid: gft$system_file_identifier,
      tape_initialization_record: dmt$tape_initialization_record;

    status.normal := TRUE;

    cmp$get_logical_unit_number (element_name, osv$deadstart_device_lun, status);
    IF NOT status.normal THEN
      RETURN;
    IFEND;

    IF io_data.unit_class = cmc$magnetic_tape_unit THEN
      tape_initialization_record.logical_unit_number := osv$deadstart_device_lun;
      tape_initialization_record.density := rmc$1600;

      iop$initialize_tape_ud (tape_initialization_record, {multiple_requests_possible} FALSE, status);
    IFEND;

  PROCEND dsp$initialize_io;
?? TITLE := 'dsp$prepare_deadstart_io', EJECT ??

{ PURPOSE:
{   This procedure is called to prepare and setup the deadstart IO device.  It also validates the
{   deadstart tape or disk file.

  PROCEDURE [XDCL] dsp$prepare_deadstart_io
    (VAR status: ost$status);

    VAR
      data_seq_p: ^SEQ ( * ),
      dummy_sfid: gft$system_file_identifier,
      element_name: cmt$element_name,
      found: boolean,
      ignore_status: ost$status,
      io_id: iot$io_id,
      io_unit_type: iot$unit_type,
      only_perform_allocates: boolean,
      product_id: cmt$product_identification,
      skip_data_p: ^SEQ ( * ),
      tape_io_status: iot$tape_io_status,
      transfer_size: integer;

    status.normal := TRUE;

    { Retrieve the product id and element name of the deadstart device.

    IF cmv$system_device_data [cmc$sdt_tape_device].specified THEN
      product_id := cmv$system_device_data [cmc$sdt_tape_device].unit_id;
      element_name := cmv$system_device_data [cmc$sdt_tape_device].unit_name;
    ELSE
      product_id := cmv$system_device_data [cmc$sdt_disk_device].unit_id;
      element_name := cmv$system_device_data [cmc$sdt_disk_device].unit_name;
    IFEND;

    { Determine whether the deadstart device is a tape or a disk.

    cmp$get_unit_type (product_id, io_data.cm_unit_type, io_unit_type, io_data.unit_class, found);
    IF NOT found THEN
      osp$set_status_abnormal (dsc$display_processor_id, dse$unknown_deadstart_device,
            'The deadstart device is unknown', status);
      RETURN;
    IFEND;

    dsp$initialize_io (element_name, status);
    IF NOT status.normal THEN
      RETURN;
    IFEND;

    { Setup or retrieve the variable that will contain all information pertaining to reading the tape
    { or disk file.  This variable is saved during the transition from boot to system core.

    IF osv$boot_is_executing THEN
      io_data.data_end_mark_read := FALSE;
      io_data.data_end_mark_reached := FALSE;
      io_data.header_information.block_type := ' ';
      io_data.header_information.record_type := ' ';
      io_data.disk.buffer_amount := 0;
      io_data.disk.file_size := 0;
      io_data.tape.blocks_read := 0;
      IF io_data.cm_unit_type = cmc$mt5682_1x THEN
        io_data.tape.maxbl := ioc$cart_tape_default_maxbl;
        io_data.tape.maximum_blocks := max_tape_blocks_cart;
        io_data.tape.current_block := max_tape_blocks_cart + 1;
      ELSE { Not cartridge tape
        io_data.tape.maxbl := ioc$max_tape_not_long_blk_lgth;
        io_data.tape.maximum_blocks := max_tape_blocks_reel;
        io_data.tape.current_block := max_tape_blocks_reel + 1;
      IFEND;
    ELSE
      data_seq_p := #SEQ (io_data);
      dsp$fetch_boot_data (dsc$io_environment, data_seq_p);
    IFEND;

   /allocate_data_areas/
    BEGIN

      { Fetch the MAU list in both cases to allow allocations to take place.

      IF osv$boot_is_executing THEN
        only_perform_allocates := (io_data.unit_class = cmc$magnetic_tape_unit);
        dsp$fetch_mau_list (only_perform_allocates, dmc$dlf_primary_entry, mau_list_p, io_data.disk.mau_count,
              transfer_size, status);
        IF NOT status.normal THEN
          EXIT /allocate_data_areas/;
        IFEND;
      IFEND;

      { Setup the IO buffer area.  Data is read from the deadstart device to this buffer area.

      CASE io_data.unit_class OF
      = cmc$magnetic_tape_unit =

        ALLOCATE tape_transfer_counts_p: [1 .. io_data.tape.maximum_blocks] IN osv$mainframe_wired_heap^;
        IF io_data.cm_unit_type = cmc$mt5682_1x THEN
          ALLOCATE io_buffer_seq_p: [[REP max_tape_data_length_cart OF cell]] IN osv$mainframe_wired_heap^;
        ELSE
          ALLOCATE io_buffer_seq_p: [[REP max_tape_data_length_reel OF cell]] IN osv$mainframe_wired_heap^;
        IFEND;

        reset_tape_data_area;
        IF osv$boot_is_executing THEN
          iop$rewind_tape (dummy_sfid, io_id, status);
          IF NOT status.normal THEN
            EXIT /allocate_data_areas/;
          IFEND;
          check_tape_status (io_id, TRUE, tape_io_status, status);
          IF NOT status.normal THEN
            EXIT /allocate_data_areas/;
          IFEND;
        IFEND;

      = cmc$mass_storage_unit =

        dsp$retrieve_device_address (max_disk_data_length, io_data.disk.device_address);
        ALLOCATE io_buffer_seq_p: [[REP max_disk_data_length OF cell]] IN osv$mainframe_wired_heap^;

        IF osv$boot_is_executing THEN
          IF (transfer_size MOD dmc$deadstart_file_alloc_size) <> 0 THEN
            osp$set_status_abnormal (dsc$display_processor_id, dse$disk_io_error,
                  'Incorrect deadstart file (transfer size error) on the system device', status);
            EXIT /allocate_data_areas/;
          IFEND;
          io_data.disk.current_address := 1;
        ELSE
          IF io_data.disk.mau_count <= 0 THEN
            osp$set_status_abnormal (dsc$display_processor_id, dse$disk_io_error,
                  'Incorrect deadstart file (MAU count) on the system device', status);
            EXIT /allocate_data_areas/;
          IFEND;
          ALLOCATE mau_list_p: [1 .. dmc$deadstart_file_alloc_size] IN osv$mainframe_wired_heap^;
        IFEND;

      ELSE
        osp$set_status_abnormal (dsc$display_processor_id, dse$unknown_deadstart_device,
              'The deadstart device is unknown', status);
        EXIT /allocate_data_areas/;
      CASEND;

      IF osv$boot_is_executing THEN
        pmp$zero_out_table (io_buffer_seq_p, #SIZE (io_buffer_seq_p^));
        dsp$save_boot_data_pointer (dsc$io_environment, #SEQ (io_data));
        dsp$save_boot_data_pointer (dsc$io_buffer, io_buffer_seq_p);
        IF io_data.unit_class = cmc$magnetic_tape_unit THEN
          dsp$save_boot_data_pointer (dsc$transfer_counts, #SEQ (tape_transfer_counts_p^));
        ELSE  {io_data.unit_class = cmc$mass_storage_unit
          dsp$save_boot_data_pointer (dsc$mau_list, #SEQ (mau_list_p^));
        IFEND;
      ELSE
        dsp$fetch_boot_data (dsc$io_buffer, io_buffer_seq_p);
        RESET io_buffer_seq_p;
        IF io_data.unit_class = cmc$magnetic_tape_unit THEN
          data_seq_p := #SEQ (tape_transfer_counts_p^);
          dsp$fetch_boot_data (dsc$transfer_counts, data_seq_p);
        ELSE  {io_data.unit_class = cmc$mass_storage_unit
          IF (max_disk_data_length - io_data.disk.buffer_amount) > 0 THEN
            NEXT skip_data_p: [[REP (max_disk_data_length - io_data.disk.buffer_amount) OF cell]]
                  IN io_buffer_seq_p;
          IFEND;
          data_seq_p := #SEQ (mau_list_p^);
          dsp$fetch_boot_data (dsc$mau_list, data_seq_p);
        IFEND;
      IFEND;

      { Setup a buffer area to read the labels.

      ALLOCATE label_seq_p: [[REP #SIZE (fst$ansi_hdr1_label) OF cell]] IN osv$mainframe_wired_heap^;

      { Position and validate the deadstart file.

      IF osv$boot_is_executing THEN
        position_deadstart_file (status);
        IF NOT status.normal THEN
          EXIT /allocate_data_areas/;
        IFEND;
      IFEND;

    END /allocate_data_areas/;

    IF NOT status.normal AND osv$boot_is_executing THEN
      dsp$cleanup_deadstart_io (ignore_status);
    IFEND;

  PROCEND dsp$prepare_deadstart_io;
?? TITLE := 'dsp$read_deadstart_device', EJECT ??

{ PURPOSE:
{   This procedure is called to retrieve an amount of data from an deadstart file.  The caller sends a
{   length describing the amount of data it wants and a pointer to an area in which the data will be
{   returned.  This procedure retrieves the desired amount of data from the IO buffer and returns it
{   to the caller.  The call may recieve less then it requested if a "data end" mark is encountered.

  PROCEDURE [XDCL, #GATE] dsp$read_deadstart_device
    (    data_length: integer;
     VAR data_buffer_p: ^SEQ ( * );
     VAR data_size_read: integer);

    VAR
      amount_to_read: integer,
      block_data_left: integer,
      block_data_p: ^SEQ ( * ),
      data_size: integer,
      data_p: ^SEQ ( * ),
      skip_data_p: ^SEQ ( * );

    IF io_data.data_end_mark_reached THEN
      data_size_read := 0;
      RETURN;
    IFEND;

    IF (data_length = 0) OR (data_buffer_p = NIL) THEN
      osp$system_error ('Incorrect request to dsp$read_deadstart_device.', NIL);
    IFEND;

    amount_to_read := data_length;
    data_size_read := 0;
    RESET data_buffer_p;

   /read_file_data/
    REPEAT

      CASE io_data.unit_class OF
      = cmc$magnetic_tape_unit =
        IF io_data.tape.current_block > io_data.tape.blocks_read THEN
          IF io_data.data_end_mark_read THEN
            EXIT /read_file_data/;
          ELSE
            read_data;
            IF io_data.data_end_mark_read AND (io_data.tape.current_block > io_data.tape.blocks_read) THEN
              EXIT /read_file_data/;
            IFEND;
          IFEND;
        IFEND;
        RESET io_buffer_seq_p TO tape_data [io_data.tape.current_block].buffer_area;
        IF io_data.tape.block_size_already_read > 0 THEN
          NEXT skip_data_p: [[REP io_data.tape.block_size_already_read OF cell]] IN io_buffer_seq_p;
        IFEND;
        block_data_left := tape_transfer_counts_p^.buffers [io_data.tape.current_block].length -
              io_data.tape.block_size_already_read;

        IF block_data_left < amount_to_read THEN
          data_size := block_data_left;
        ELSE
          data_size := amount_to_read;
        IFEND;
        IF data_size = 0 THEN
          osp$system_error ('Incorrect deadstart tape.', NIL);
        IFEND;
        NEXT block_data_p: [[REP data_size OF cell]] IN io_buffer_seq_p;
        NEXT data_p: [[REP data_size OF cell]] IN data_buffer_p;
        data_p^ := block_data_p^;

        data_size_read := data_size_read + data_size;
        amount_to_read := amount_to_read - data_size;
        io_data.tape.block_size_already_read := io_data.tape.block_size_already_read + data_size;
        IF io_data.tape.block_size_already_read >=
              tape_transfer_counts_p^.buffers [io_data.tape.current_block].length THEN
          io_data.tape.current_block := io_data.tape.current_block + 1;
          io_data.tape.block_size_already_read := 0;
        IFEND;

      = cmc$mass_storage_unit =
        IF amount_to_read > io_data.disk.file_size THEN
          amount_to_read := io_data.disk.file_size;
        IFEND;
        IF io_data.disk.buffer_amount <= 0 THEN
          read_data;
        IFEND;
        IF io_data.disk.file_size <= 0 THEN
          io_data.data_end_mark_read := TRUE;
          EXIT /read_file_data/;
        IFEND;

        IF io_data.disk.buffer_amount < amount_to_read THEN
          data_size := io_data.disk.buffer_amount;
        ELSE
          data_size := amount_to_read;
        IFEND;
        IF data_size = 0 THEN
          osp$system_error ('Incorrect deadstart tape.', NIL);
        IFEND;
        NEXT block_data_p: [[REP data_size OF cell]] IN io_buffer_seq_p;
        NEXT data_p: [[REP data_size OF cell]] IN data_buffer_p;
        data_p^ := block_data_p^;

        data_size_read := data_size_read + data_size;
        amount_to_read := amount_to_read - data_size;
        io_data.disk.buffer_amount := io_data.disk.buffer_amount - data_size;
        io_data.disk.file_size := io_data.disk.file_size - data_size;

      ELSE
        osp$system_error ('The deadstart device is unknown', NIL);
      CASEND;

    UNTIL amount_to_read <= 0; {read_file_data
    RESET data_buffer_p;

    CASE io_data.unit_class OF
    = cmc$magnetic_tape_unit =
      io_data.data_end_mark_reached := (io_data.data_end_mark_read AND
            (io_data.tape.current_block > io_data.tape.blocks_read));
    = cmc$mass_storage_unit =
      io_data.data_end_mark_reached := (io_data.data_end_mark_read AND (io_data.disk.file_size <= 0));
    ELSE
    CASEND;

  PROCEND dsp$read_deadstart_device;
?? TITLE := 'dsp$read_header_labels', EJECT ??

{ PURPOSE:
{   This procedure is called to read the header labels from the deadstart file.  The procedure
{   will first make sure that the last file's data end mark has been reached.  The procedure then
{   will skip over the EOFN labels, read the HDR1 and HDR2 labels and then skip to the data end mark.

  PROCEDURE [XDCL, #GATE] dsp$read_header_labels
    (VAR file_identifier: dst$deadstart_file_identifier);

    VAR
      hdr1_label_p: ^fst$ansi_hdr1_label,
      hdr2_label_p: ^fst$ansi_hdr2_label,
      label_found: boolean,
      label_id: deadstart_label_id;

    { Skip over any unused data on the individual file.

    skip_to_next_data_end;

    { Skip over the EOFN records.

    skip_to_next_data_end;

    io_data.disk.file_size := #SIZE (fst$ansi_hdr1_label) + #SIZE (fst$ansi_hdr2_label);

    { Read the HDR1 label and retrieve the file identifier.

    read_label (label_seq_p, label_id, label_found);
    IF NOT label_found OR (label_id <> 'HDR1') THEN
      osp$system_error ('Incorrect deadstart file: No HDR1 label', NIL);
    IFEND;
    NEXT hdr1_label_p IN label_seq_p;
    file_identifier := hdr1_label_p^.file_identifier;

    { Read the HDR2 label and retrieve the preceeding file size for disk deadstarts.

    read_label (label_seq_p, label_id, label_found);
    IF NOT label_found OR (label_id <> 'HDR2') THEN
      osp$system_error ('Incorrect deadstart file: No HDR2 label', NIL);
    IFEND;
    NEXT hdr2_label_p IN label_seq_p;
    io_data.header_information.block_type := hdr2_label_p^.ve_block_type;
    io_data.header_information.record_type := hdr2_label_p^.ve_record_type;

    { Skip over any more HDRN labels.

    skip_to_next_data_end;

    IF io_data.unit_class = cmc$mass_storage_unit THEN
      convert_string_to_file_size (hdr2_label_p^);
    IFEND;

  PROCEND dsp$read_header_labels;
?? TITLE := 'dsp$retrieve_device_address', EJECT ??

{ PURPOSE:
{   This procedure is used to retrieve the device address from the logical unit.

  PROCEDURE [XDCL] dsp$retrieve_device_address
    (    transfer_size: integer;
     VAR device_address: dmt$ms_logical_device_address);

    VAR
      physical_attributes_p: ^dmt$physical_device_attributes,
      status: ost$status;

    PUSH physical_attributes_p: [1 .. 2];
    physical_attributes_p^ [1].keyword := dmc$bytes_per_mau;
    physical_attributes_p^ [2].keyword := dmc$maus_per_cylinder;
    dmp$get_physical_attributes (cmv$system_device_data [cmc$sdt_disk_device].unit_id, physical_attributes_p,
          status);
    IF NOT status.normal THEN
      osp$system_error ('Disk initialization failed', ^status);
    IFEND;

    cmp$get_logical_unit_number (cmv$system_device_data [cmc$sdt_disk_device].unit_name,
          device_address.logical_unit_number, status);
    IF NOT status.normal THEN
      osp$system_error ('Error in getting logical unit number', ^status);
    IFEND;
    device_address.maus_per_position := physical_attributes_p^ [2].maus_per_cylinder;
    device_address.transfer_length := transfer_size DIV physical_attributes_p^ [1].bytes_per_mau;
    device_address.transfer_mau_offset := 0;
    device_address.write_translation := FALSE;

  PROCEND dsp$retrieve_device_address;
?? TITLE := 'dsp$retrieve_header_information', EJECT ??

{ PURPOSE:
{   This procedure retrieves information from the tape label.  A disk deadstart file contains the
{   same label format.

  PROCEDURE [XDCL, #GATE] dsp$retrieve_header_information
    (VAR header_information: dst$header_information);

    IF io_data.header_information.block_type = 'US' THEN
      header_information.block_type := amc$user_specified;
    ELSE
      header_information.block_type := amc$system_specified;
    IFEND;

    IF io_data.header_information.record_type = 'V' THEN
      header_information.record_type := amc$variable;
    ELSE
      header_information.record_type := amc$undefined;
    IFEND;

  PROCEND dsp$retrieve_header_information;
MODEND dsm$deadstart_io;
