1
0
mirror of https://github.com/livingcomputermuseum/UniBone.git synced 2026-01-11 23:52:51 +00:00
Josh Dersch e037b0d36d Added dato_mask to allow discerning DATOB operations to emulated registers.
This allows the RH11's RHCS1 register to function properly.  2.11BSD now boots!
2020-03-25 07:06:54 +01:00

964 lines
34 KiB
C++

/* rl11.cpp: Implementation of the RL11 controller
Copyright (c) 2018, Joerg Hoppe
j_hoppe@t-online.de, www.retrocmp.com
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
JOERG HOPPE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12-nov-2018 JH entered beta phase
- implements a 4 UNIBUS register interface, which are shared with PRU.
- gets notified of UNIBUS register access on_after_register_access()
- starts 4 RL01/02 drives
on_after_register_access() is a high priority RT thread.
It may ONLY update the settings of UNIBUS interface registers by swapping in
several internal registers (status for each drive, MP multipuprpose for different
Commands) to UNIBUS registers.
- execution of commands, access to drives etc is made in worker()
worker() is waked by a signal from on_after_register_access()
Todo:
- operation, when drive power OFF? error DSE drive select?
1) RL0 powered off: CS =200, nach NOP
Get status; DA=013, write 00004 Read: CS 102204 (ERR,OPI, DRVready=0
MP = 006050 (3x identisch)
Seek: Write 0006, read: 102206 (Spnerror, coveropen,brush home)
MP = 20210
2) RL0 powered on, LOAD
NOOP: CS write 0, read 200
Get Status: DA=013,write 0004, read 204. MP = 20217 (spinn down), dann 20210 (LOAD)
Seek: Write CS=0006 , read 102206. MP = unchanged
3) RL02 on track
NOOP: CS write 0, read 140201 (Driverer, do Getstatus
Get Status: DA=013, write 0004, read 205. MP = 020235
Seek: DA=0377 (255)100, read = 207
- Which errors raise "OPI" (operation incomplete)
NXM?
- Mismatch DMA wordcount and sector buffer
word len != sector border ? => no problem?
end of track before worldcount == 0 ?
"DA register is not incrmeneted in multisector transfer"
=> OPI?
- "Read header: ": select which sector to read?
Simulate disk rotation???
How to generate CRC? -> simh!
- "read data without header"
-> wait for sector pulse? disk rotation?
Communication between on_after_register_access and worker():
- use pthread condition variable pthrad_cond_*
- normally a mutex show protect worker() against variable change
by interrupting on_after_register_access()
- the signal "controller_ready" is that mutex already:
set by cmd-start in on_after_register_access(),
released by worker() on completion
- still a mutex needed, only for the thread condition variable as shown in
- mutex in on_after_register_access() and worker()
- all refgsier access are atomic 32bit anyhow
http://openbook.rheinwerk-verlag.de/linux_unix_programmierung/Kap10-006.htm#RxxKap10006040003201F02818E
https://docs.oracle.com/cd/E19455-01/806-5257/6je9h032r/index.html#sync-59145
*/
#include <stdio.h>
#include <assert.h>
#include <pthread.h>
#include <sys/time.h>
#include <string>
#include "logger.hpp"
#include "gpios.hpp"
#include "utils.hpp"
#include "unibus.h"
#include "unibusadapter.hpp"
#include "panel.hpp"
#include "rl11.hpp"
#include "rl0102.hpp"
// function codes
#define CMD_NOOP 0
#define CMD_WRITE_CHECK 1
#define CMD_GET_STATUS 2
#define CMD_SEEK 3
#define CMD_READ_HEADER 4
#define CMD_WRITE_DATA 5
#define CMD_READ_DATA 6
#define CMD_READ_DATA_WITHOUT_HEADER_CHECK 7
// state of controller
#define RL11_STATE_CONTROLLER_READY 0 // can accept new commands
#define RL11_STATE_CONTROLLER_BUSY 1 // unspecified operation, temporary
// #define RL11_STATE_CONTROLLER_DONE RL11_STATE_CONTROLLER_READY
// #define RL11_STATE_CONTROLLER_DONE 2 // not busy, but still not able to accept new command
// seek states
#define RL11_STATE_SEEK_MASK 0x100 // bit marks all seek states
#define RL11_STATE_SEEK_INIT 0x101
// "read/write" states
#define RL11_STATE_RW_MASK 0x0200 // bit marks all READ/WRITE states
#define RL11_STATE_RW_INIT 0x0201
#define RL11_STATE_RW_DISK 0x0202
//#define RL11_STATE_RW_WAIT_DMA 0x0203
//#define RL11_STATE_RW_DONE 0x0204
RL11_c::RL11_c(void) :
storagecontroller_c() {
unsigned i;
state = RL11_STATE_CONTROLLER_READY;
name.value = "rl"; // only one supported
type_name.value = "RL11";
log_label = "rl";
// base addr, intr-vector, intr level
set_default_bus_params(0774400, 15, 0160, 5);
// add 4 RL disk drives
drivecount = 4;
for (i = 0; i < drivecount; i++) {
RL0102_c *drive = new RL0102_c(this);
drive->unitno.value = i; // set the number plug
drive->name.value = name.value + std::to_string(i);
drive->log_label = drive->name.value;
drive->parent = this; // link drive to controller
storagedrives.push_back(drive);
}
// create UNIBUS registers
register_count = 4;
// Control Status: reg no = 0, offset +0
busreg_CS = &(this->registers[0]);
strcpy_s(busreg_CS->name, sizeof(busreg_CS->name), "CS");
busreg_CS->active_on_dati = false; // can be read fast without ARM code, no state change
busreg_CS->active_on_dato = true; // writing changes controller state
busreg_CS->reset_value = 0x80; // read default: bit 7 set
busreg_CS->writable_bits = 0x3fe; // bits 9..1 writable
// Bus Address: offset +2
busreg_BA = &(this->registers[1]);
strcpy_s(busreg_BA->name, sizeof(busreg_BA->name), "BA");
busreg_BA->active_on_dati = false; // pure storage
busreg_BA->active_on_dato = false;
busreg_BA->reset_value = 0; // read default: bit 7 set
busreg_BA->writable_bits = 0xfffe; // bits 15..1 writable
// disk address: offset +4
busreg_DA = &(this->registers[2]);
strcpy_s(busreg_DA->name, sizeof(busreg_DA->name), "DA");
busreg_DA->active_on_dati = false; // pure storage
busreg_DA->active_on_dato = false;
busreg_DA->reset_value = 0;
busreg_DA->writable_bits = 0xffff; // 16 bit read/write, format depnds on usage
// Multi Purpose: offset +6
busreg_MP = &(this->registers[3]);
strcpy_s(busreg_MP->name, sizeof(busreg_MP->name), "MP");
busreg_MP->active_on_dati = true; // read: needs logic for 3 word-sequence
busreg_MP->active_on_dato = true; // write: just param word for cmds
busreg_MP->reset_value = 0;
busreg_MP->writable_bits = 0xffff; // 16 bit read only
}
RL11_c::~RL11_c() {
unsigned i;
for (i = 0; i < drivecount; i++)
delete storagedrives[i];
}
bool RL11_c::on_param_changed(parameter_c *param) {
if (param == &enabled) {
if (enabled.new_value) {
// enabled
connect_to_panel();
} else {
// disabled
disconnect_from_panel();
}
} else if (param == &priority_slot) {
dma_request.set_priority_slot(priority_slot.new_value);
intr_request.set_priority_slot(priority_slot.new_value);
} else if (param == &intr_level) {
intr_request.set_level(intr_level.new_value);
} else if (param == &intr_vector) {
intr_request.set_vector(intr_vector.new_value);
}
return storagecontroller_c::on_param_changed(param); // more actions (for enable)
}
/* connect parameters of drives to i2c paneldriver
* Changes to parameter values after user panel operation
* or refresh_params_from_panel()
* TODO: virtual in base class
*/
void RL11_c::connect_to_panel() {
if (drivecount != 4)
FATAL("RL11 must control exactly 4 RL drives");
/* Connection matrix: See construction of I2C paneldriver:
16 lamps and 8 buttons connected to 2 MC23017 GPIO extender
Chipaddr, reg_addr
*/
for (int drive_no = 0; drive_no < 4; drive_no++) {
panelcontrol_c *pc;
RL0102_c *drive = dynamic_cast<RL0102_c *>(storagedrives[drive_no]);
// luckily paneldriver controls are named like drive->parameters ...
pc = paneldriver->control_by_name(drive->name.value, drive->runstop_button.name);
paneldriver->link_control_to_parameter(&drive->runstop_button, pc);
pc = paneldriver->control_by_name(drive->name.value, drive->load_lamp.name);
paneldriver->link_control_to_parameter(&drive->load_lamp, pc);
pc = paneldriver->control_by_name(drive->name.value, drive->ready_lamp.name);
paneldriver->link_control_to_parameter(&drive->ready_lamp, pc);
pc = paneldriver->control_by_name(drive->name.value, drive->fault_lamp.name);
paneldriver->link_control_to_parameter(&drive->fault_lamp, pc);
pc = paneldriver->control_by_name(drive->name.value, drive->writeprotect_lamp.name);
paneldriver->link_control_to_parameter(&drive->writeprotect_lamp, pc);
pc = paneldriver->control_by_name(drive->name.value, drive->writeprotect_button.name);
paneldriver->link_control_to_parameter(&drive->writeprotect_button, pc);
}
}
/* unconnect drives from i2c paneldriver */
void RL11_c::disconnect_from_panel() {
for (int drive_no = 0; drive_no < 4; drive_no++) {
RL0102_c *drive = dynamic_cast<RL0102_c *>(storagedrives[drive_no]);
paneldriver->unlink_controls_from_device(drive);
}
}
// force param values of all drives as set by panel.
void RL11_c::refresh_params_from_panel() {
for (int drive_no = 0; drive_no < 4; drive_no++) {
RL0102_c *drive = dynamic_cast<RL0102_c *>(storagedrives[drive_no]);
paneldriver->refresh_params(drive);
}
}
// short alias
RL0102_c *RL11_c::selected_drive(void) {
return dynamic_cast<RL0102_c *>(storagedrives[selected_drive_unitno]);
}
// reset controller, after installation, on power and on INIT
void RL11_c::reset(void) {
// MPR = mpr_silo[0] is not reset
busreg_MP->reset_value = busreg_MP->active_dati_flipflops;
reset_unibus_registers();
busreg_MP->reset_value = 0; // cleared on power cycle
DEBUG("RL11_c::reset()");
// reset internal state
selected_drive_unitno = 0;
function_code = 0;
interrupt_enable = 0;
unibus_address_msb = 0;
clear_errors();
intr_request.edge_detect_reset();
change_state(RL11_STATE_CONTROLLER_READY);
// or do_command_done() ?
do_controller_status(false, __func__);
}
void RL11_c::clear_errors() {
error_dma_timeout = false;
error_operation_incomplete = false;
error_writecheck = false;
error_header_not_found = false;
}
// busaddress <17:16> = CS<4:5>
uint32_t RL11_c::get_unibus_address() {
return (unibus_address_msb << 16) | get_register_dato_value(busreg_BA);
}
// set the changed current DMA unibus address
void RL11_c::update_unibus_address(uint32_t addr) {
unibus_address_msb = (addr >> 16); // bit 17,16 used if CS is calculated
set_register_dati_value(busreg_BA, addr & 0xfffe, __func__);
// bits 17&16 in CS returned with do_controller_status()
}
// eval 2's complement value in MP register
// doc says: "bits13-15 must be ones": count value <= 0x2000
// but R11 v5.5 violates this rule.
uint16_t RL11_c::get_MP_wordcount() {
uint16_t result = (0x10000 - get_register_dato_value(busreg_MP)) & 0xffff;
// assert(result <= 0x2000); // RT11 v5.5 boot?
return result;
}
void RL11_c::set_MP_wordcount(uint16_t wordcount) {
// word count in 2's complement, bits 15:13 always set
// assert(wordcount <= 0x1fff); // RT11 v5.5 boot?
// do not change MP value visible with DATI
busreg_MP->active_dato_flipflops = (0x10000 - wordcount) & 0xffff;
}
// data read from MP register comes from a 3 word silo.
// a word must be put in the whole silo
void RL11_c::set_MP_dati_value(uint16_t w, const char *debug_info) {
mpr_silo[0] = mpr_silo[1] = mpr_silo[2] = w;
mpr_silo_idx = 0;
set_register_dati_value(busreg_MP, w, debug_info);
}
// activate MP output sequence for filled silo
void RL11_c::set_MP_dati_silo(const char *debug_info) {
mpr_silo_idx = 0;
set_register_dati_value(busreg_MP, mpr_silo[0], debug_info);
}
// Access to UNIBUS register interface
// called with 100% CPU highest RT priority.
// UNIBUS is stopped by SSYN while this is running.
// No loops! no drive, console, file or other operations!
// UNIBUS DATO cycles let dati_flipflops "flicker" outside of this proc:
// do not read back dati_flipflops.
void RL11_c::on_after_register_access(unibusdevice_register_t *device_reg,
uint8_t unibus_control, uint16_t dato_mask) {
UNUSED(dato_mask);
// on drive select:
// move status of new drive to controller status register
// on command: signal worker thread
switch (device_reg->index) {
case 0: { // CS
if (unibus_control == UNIBUS_CONTROL_DATO) {
//GPIO_SETVAL(gpios.led[0], 1); // inverted, led OFF
// only write changes state
// wait until worker() is ready to accept signal,
// only allowed if RL11_STATE_CONTROLLER_READY
// Write into CSR is seems blocked if not controller READY (GO H)
// FPMS PS2: GO inhibits WRT CSR L over E37, E70
// Regular PDP-11 software should only poll CS until ready, not write before.
if (state != RL11_STATE_CONTROLLER_READY)
break; // ignore write
pthread_mutex_lock(&on_after_register_access_mutex);
assert(state == RL11_STATE_CONTROLLER_READY); // else blocked by mutex
// CS<8:9> = drive select
selected_drive_unitno = (busreg_CS->active_dato_flipflops >> 8) & 0x03;
// CS<1:3> is cmd
function_code = (busreg_CS->active_dato_flipflops >> 1) & 0x07;
// CS <4:5> is address<16:17>
unibus_address_msb = (busreg_CS->active_dato_flipflops >> 4) & 0x03;
// CS<6> is IE
interrupt_enable = !!(busreg_CS->active_dato_flipflops & (1 << 6));
// CRDY is CS<7>
bool new_controller_ready = !!(busreg_CS->active_dato_flipflops & (1 << 7));
// accept only command if controller ready
if (new_controller_ready) {
// GO not set
do_controller_status(false, __func__); // UNIBUS sees still "controller ready"
} else {
RL0102_c *drive; // some funct need the selected drive
bool execute_function_delayed;
// if (interrupt_enable && busreg_CS->active_dato_flipflops == 0100) // ZRLLG@21636
// GPIO_SETVAL(gpios.led[0], 0); // inverted, led ON
// TODO: can these functions be executed when seek is pending?
// GO !
clear_errors();
// some function cause an interrupt immediately (in this UNIBUS cycle):
change_state(RL11_STATE_CONTROLLER_BUSY); // force BUSY->READY INTR
execute_function_delayed = false;
switch (function_code) {
case CMD_NOOP:
DEBUG("cmd %d = Noop", function_code);
do_command_done();
break;
case CMD_SEEK:
drive = selected_drive();
if ((drive->status_word & 0x07) == RL0102_STATE_seek)
// if waiting for end of seek: execute seek in worker()
execute_function_delayed = true;
else {
DEBUG("cmd %d = Seek", function_code);
state_seek();
}
break;
case CMD_GET_STATUS:
drive = selected_drive();
// SIMH: OPI if DA code not 3? Real RL11: just NOOP
DEBUG("cmd %d = Get Status. DA=%06o.", function_code,
get_register_dato_value(busreg_DA));
// doc says: bits 0,4:7 must be 0. SimH checks only bit 1 and 3 for "1"
// XXDP boot: seen 001217 and 00646
if ((get_register_dato_value(busreg_DA) & 0x02) != 0x02) { // bit<0:1> must be 1,
// if ((get_register_dato_value(busreg_DA) & 0xf7) != 0x03) { // bit<0:1> must be 1,
do_operation_incomplete("DA bit 2 not set");
} else {
if (get_register_dato_value(busreg_DA) & 0x08) // bit 3: reset status?
drive->clear_error_register();
set_MP_dati_value(drive->status_word, __func__);
}
do_command_done();
break;
default:
execute_function_delayed = true;
}
if (execute_function_delayed) {
// long running command (or delayed seek), involving disk activity:
// run at lower priority
// signal worker() with pthread condition variable
// transition from high priority "unibusadapter thread" to
// standard "device thread".
do_controller_status(false, __func__); // UNIBUS sees now "controller not ready"
// wake up worker()
pthread_cond_signal(&on_after_register_access_cond);
}
}
pthread_mutex_unlock(&on_after_register_access_mutex);
} else {
// CS reg is not "active_on_dati"
// set value in code with "set_register_dati_value(reg_CS, CS_read) ;"
}
break;
}
case 3: // MP
if (unibus_control == UNIBUS_CONTROL_DATI) {
// return data from 3 word silo
// assume: silo[0] read from dati-flipflops, now post read increment dati flipflops
// MP port read. update MP with next sequential value from SILO
if (mpr_silo_idx < 2) { // read header: MP is port to 3 words
// next DATI reads next value
set_register_dati_value(device_reg, mpr_silo[++mpr_silo_idx], __func__);
} else {
// 3rd or later access: no further inc, return always SILO[2]
// until MP set with one "set_MP_*()" function
set_register_dati_value(device_reg, mpr_silo[2], __func__);
}
} else {
// value written by DATO are parameters for cmds, they do not change state
// but restore value readable with DATI
assert(busreg_MP->shared_register->value == busreg_MP->active_dati_flipflops);
}
break;
}
// now SSYN goes inactive !
}
void RL11_c::on_power_changed(void) {
// storagecontroller_c forwards to drives
storagecontroller_c::on_power_changed();
if (power_down) {
// power-on - defaults
reset();
// but I need a valid state before that.
}
}
// UNIBUS INIT: clear some registers, not all error conditions
void RL11_c::on_init_changed(void) {
// storagecontroller_c forwards to drives
storagecontroller_c::on_init_changed();
// write all registers to "reset-values"
if (!init_asserted) // falling edge of INIT
reset();
}
// called by drive if ready or error
// must update CS then
void RL11_c::on_drive_status_changed(storagedrive_c *drive) {
if (drive->unitno.value != selected_drive_unitno)
return;
// show status lines in CS for selected drive
do_controller_status(false, __func__);
}
// issue interrupt.
// do not set CONTROLLER READY bit
void RL11_c::do_command_done(void) {
// bool do_int = false;
if (interrupt_enable && state != RL11_STATE_CONTROLLER_READY)
change_state_INTR(RL11_STATE_CONTROLLER_READY);
else
// no intr
change_state(RL11_STATE_CONTROLLER_READY);
#ifdef OLD
if (do_int) {
// first set visible "controller ready"
/* Change of CS->DATI visible value and raise of Interrupt must be atomic
* Else ZRLK: polls CS for ready, continues operation,
* RDY interrupt too late and at wrong program position
*/
// worker_boost_realtime_priority();
change_state(RL11_STATE_CONTROLLER_READY);
// scheduler may inject time here, if called from low prio worker() !
// pending interrupt triggered
//TODO: connect to interrupt register busreg_CS
unibusadapter->INTR(intr_request, NULL, 0);
DEBUG("Interrupt!");
// worker_restore_realtime_priority();
} else
// no intr
change_state(RL11_STATE_CONTROLLER_READY);
/*
{
// interrupt on leading edge of "controller-ready" signal
DEBUG("Interrupt!");
interrupt();
}
do_controller_status(__func__);
change_state(RL11_STATE_CONTROLLER_READY);
*/
#endif
}
// CS read/Write access different registers.
// write current status into CS, for next read operation
// must be done after each DATO
void RL11_c::do_controller_status(bool do_intr, const char *debug_info) {
RL0102_c *drive = selected_drive();
uint16_t tmp = 0;
bool drive_error_any = drive->drive_error_line; // save, may change
bool controller_ready = (state == RL11_STATE_CONTROLLER_READY);
//bit 0: drive ready
if (drive->drive_ready_line)
tmp |= BIT(0);
// bits <1:3>: function code
tmp |= (function_code << 1);
// bits <4:5>: bus_address <17:16>
tmp |= (unibus_address_msb & 3) << 4;
// bit 6: IE
if (interrupt_enable)
tmp |= BIT(6);
// bit 7: CRDY
if (controller_ready)
tmp |= BIT(7);
// bits <8:9>: drive select
tmp |= (selected_drive_unitno << 8);
// bit <10:13>: error code. Only some possible errors
if (error_operation_incomplete)
tmp |= (0x01) << 10; // error code "OPI" = 0001
if (error_writecheck)
tmp |= (0x02) << 10; // errror code "Read Data CRC" = 0010
if (error_header_not_found)
tmp |= (0x05) << 10; // error code "HNF" = 0101
if (error_dma_timeout)
tmp |= (0x08) << 10; // error code "NXM" = 1000
// bit 14 is drive error
if (drive_error_any)
tmp |= BIT(14);
// bit 15 is composite error
if (error_dma_timeout || error_operation_incomplete || error_writecheck
|| error_header_not_found || drive_error_any) {
tmp |= BIT(15);
}
if (do_intr) {
// set CSR atomically with INTR signal lines
assert(interrupt_enable);
assert(controller_ready);
unibusadapter->INTR(intr_request, busreg_CS, tmp);
} else
set_register_dati_value(busreg_CS, tmp, debug_info);
// now visible on UNIBUS
}
// if the drive is powered of, or not READY, it will not answer to RL11 requests
// then call do_operation_incomplete()
void RL11_c::do_operation_incomplete(const char *info) {
DEBUG("do_operation_incomplete! %s", info);
// drive does not respond after 200ms
timeout.wait_ms(200 / emulation_speed.value);
error_operation_incomplete = true;
do_command_done();
}
// separate proc, to have a testpoint
void RL11_c::change_state(unsigned new_state) {
if (state != new_state)
DEBUG("Change RL11 state from 0x%x to 0x%x.", state, new_state);
state = new_state;
do_controller_status(false, __func__);
}
void RL11_c::change_state_INTR(unsigned new_state) {
if (state != new_state)
DEBUG("Change RL11 state from 0x%x to 0x%x.", state, new_state);
state = new_state;
do_controller_status(true, __func__);
}
// start seek operation, then interrupt
// only one state, but complex operation
void RL11_c::state_seek() {
RL0102_c *drive = selected_drive();
// eval difference word in DA to destination_cylinder
// what, if drive not ready???
if (!drive->drive_ready_line) {
do_operation_incomplete("state_seek(): drive not ready"); // verified
return;
}
// bit 0 must be 1, bit 3 must be 0
if ((get_register_dato_value(busreg_DA) & 9) != 1) {
// do nothing
do_command_done();
return;
}
// cylinder address difference is <7:15>
unsigned cyl_diff = get_register_dato_value(busreg_DA) >> 7;
// direction is bit 2
unsigned direction_to_spindle = (get_register_dato_value(busreg_DA) >> 2) & 1;
unsigned destination_cylinder, destination_head;
// if destination cylinder out of range:
// "stop at guard band and retreat to first even numbered track"
// so cyl > 511 : cylinder := 510 !, head = unchanged . Verified.
if (direction_to_spindle) { // to higher cylinder
destination_cylinder = drive->cylinder + cyl_diff;
if (destination_cylinder >= drive->cylinder_count)
destination_cylinder = (drive->cylinder_count - 1) & 0xfffe;
} else { // to lower cylinder
if (cyl_diff > drive->cylinder)
destination_cylinder = 0; // bound hit
else
destination_cylinder = drive->cylinder - cyl_diff;
}
// bit<4> is head
destination_head = (get_register_dato_value(busreg_DA) >> 4) & 1;
bool ok = drive->cmd_seek(destination_cylinder, destination_head);
if (!ok) {
// drive in wrong state??
}
do_command_done();
}
// data from drive is requested with cmd_read_next_sector()
// the drive simulates disk rotation by returning content of next sector header
// together with sector data into "cur_sector_hader and cur_sector_data
// (on the original this is a sequence: first header data into SILO, then sector data)
//
// read data sector by sector from drive into SILO
// after each sector
// -> DMA transaction for sector
// perhaps a DATA LATE if previous DMA not ready
// increment diskaddress, read next sector.
// disk drive is guaranteed to need time_per_sector_us
void RL11_c::state_readwrite() {
RL0102_c *drive = selected_drive();
uint16_t disk_address = get_register_dato_value(busreg_DA);
uint32_t unibus_address = get_unibus_address(); // device register to local var
unsigned sector_wordcount = drive->sector_size_bytes / 2; // size of sector
unsigned cmd_wordcount = get_MP_wordcount(); // wordcount in hidden MP register
unsigned dma_wordcount; // len of current DMA transaction
assert(sizeof(silo) / 2 >= sector_wordcount);
assert(
function_code == CMD_READ_DATA_WITHOUT_HEADER_CHECK || function_code == CMD_READ_DATA || function_code == CMD_WRITE_DATA || function_code == CMD_WRITE_CHECK);
if (!drive->drive_ready_line) {
do_operation_incomplete("state_readwrite(): drive not ready"); // verified
return;
}
switch (state) {
case RL11_STATE_RW_INIT:
// Entry condition: cmd_wordcount > 0
// diskaddress DA valid.
// setup controller at start of read operation
clear_errors();
if (cmd_wordcount == 0)
do_command_done();
else
change_state(RL11_STATE_RW_DISK);
break;
case RL11_STATE_RW_DISK:
// start next sector read, or terminate
assert(cmd_wordcount > 0);
if (function_code == CMD_READ_DATA_WITHOUT_HEADER_CHECK) {
// just read next sector data block from disk, disk address ignored
} else {
// Disk address valid?
if (!drive->header_on_track(disk_address)) {
// - sector not on current drive track: search header forever => OPI
// - No spiral read/write: if reading past end of track: sector number is incremented to 40 = 050.
// no track change, no head switch, instead OPI error.
// - advance past last sector on track: error OPI
error_header_not_found = true;
do_operation_incomplete("RL11_STATE_RW_DISK: !drive->header_on_track()");
break;
}
// wait for right sector header
drive->cmd_read_next_sector_header((uint16_t *) mpr_silo, 3);
if (mpr_silo[0] != get_register_dato_value(busreg_DA))
break; // wrong sector
// DEBUG(LC_RL, "Found sector header DA=%06o.", silo[0]);
}
// # of words to read/write from/to memory
if (cmd_wordcount > sector_wordcount)
dma_wordcount = sector_wordcount;
else
dma_wordcount = cmd_wordcount; // transfer all remaining words
memset((uint8_t *) silo, 0, sizeof(silo));
memset((uint8_t *) silo_compare, 0, sizeof(silo_compare));
if (function_code == CMD_READ_DATA
|| function_code == CMD_READ_DATA_WITHOUT_HEADER_CHECK) {
// the requested sector passes the head: read it into the SILO
drive->cmd_read_next_sector_data(silo, 128);
//logger.debug_hexdump(LC_RL, "Read data between disk access and DMA",
// (uint8_t *) silo, sizeof(silo), NULL);
// start DMA transmission of SILO into memory
unibusadapter->DMA(dma_request, true, UNIBUS_CONTROL_DATO, unibus_address, silo,
dma_wordcount);
error_dma_timeout = !dma_request.success;
unibus_address = dma_request.unibus_end_addr;
} else if (function_code == CMD_WRITE_CHECK) {
// read sector data to compare with sector data
drive->cmd_read_next_sector_data(silo, 128);
// logger.debug_hexdump(LC_RL, "Read data between disk access and DMA",
// (uint8_t *) silo, sizeof(silo), NULL);
// start DMA transmission of memory to compare with SILO
unibusadapter->DMA(dma_request, true, UNIBUS_CONTROL_DATI, unibus_address,
silo_compare, dma_wordcount);
error_dma_timeout = !dma_request.success;
unibus_address = dma_request.unibus_end_addr;
} else if (function_code == CMD_WRITE_DATA) {
// start DMA transmission of memory into SILO
unibusadapter->DMA(dma_request, true, UNIBUS_CONTROL_DATI, unibus_address, silo,
dma_wordcount);
error_dma_timeout = !dma_request.success;
unibus_address = dma_request.unibus_end_addr;
}
// request_client_DMA() was blocking, DMA processed now.
// unibus_address updated to last accesses address
unibus_address += 2; // was last address, is now next to fill
// if timeout: yes, current addr is addr AFTER illegal address (verified)
update_unibus_address(unibus_address); // set addr msb to cs
if (error_dma_timeout) {
// NXM condition
do_operation_incomplete("RL11_STATE_RW_WAIT_DMA: dma timeout");
break;
}
if (function_code == CMD_WRITE_DATA) { // data from SILO to disk
// write whole silo. if less data read from memory, 00s are filled in.
drive->cmd_write_next_sector_data(silo, 128);
//logger.debug_hexdump(LC_RL, "Write data between DMA and disk access",
// (uint8_t *) silo, sizeof(silo), NULL);
} else if (function_code == CMD_WRITE_CHECK) {
// compare data from disk in silo[] with data from memory in silo_compare[]
unsigned i;
error_writecheck = false;
// compare only full sectors, even if not all words of sector read:
// silo and silo_compare partly filled but 00 initialized
for (i = 0; i < sector_wordcount; i++)
if (silo[i] != silo_compare[i])
error_writecheck = true;
}
if (function_code == CMD_READ_DATA_WITHOUT_HEADER_CHECK) {
// "The DA register is not incremented"
// But ZRLHB0, test 44: DA should increment by 1
disk_address++;
} else {
// increment "disk address" to next sector on track.
// may be invalid now, then "OPI" in next loop.
disk_address++;
}
set_register_dati_value(busreg_DA, disk_address, __func__);
// sector read & transfer OK. next?
if (cmd_wordcount >= sector_wordcount) // start DMA transmission of SILO into memory
cmd_wordcount -= sector_wordcount;
else
cmd_wordcount = 0;
set_MP_wordcount(cmd_wordcount);
if (cmd_wordcount == 0) {
// last sector transfered
do_command_done();
// READY/INTR delayed against end of DMA: nanosleep() in worker()
break;
}
change_state(RL11_STATE_RW_DISK);
break;
default:
ERROR("RL11:state_readwrite(): illegal state %d.", state);
}
// update visible state: local var to UNIBUS register
// update_unibus_address(unibus_address);
}
// thread
// excutes commands
void RL11_c::worker(unsigned instance) {
UNUSED(instance); // only one
assert(!pthread_mutex_lock(&on_after_register_access_mutex));
// set prio to RT, but less than unibus_adapter
worker_init_realtime_priority(rt_device);
while (!workers_terminate) {
/* process command state machine in parallel with
"active register" state changes
*/
// wait for "cmd" signal of on_after_register_access()
int res;
// CRDY in busreg_CS->active_dati_flipflops & 0x80))
// may still be inactive, when PRU updatesit with iNTR delayed.
// enable operation of pending on_after_register_access()
/*
if (!(busreg_CS->active_dati_flipflops & 0x80)) { // CRDY must be set
ERROR("CRDY not set, CS=%06o", busreg_CS->active_dati_flipflops);
logger->dump(logger->default_filepath);
}
*/
res = pthread_cond_wait(&on_after_register_access_cond,
&on_after_register_access_mutex);
if (res != 0) {
ERROR("RL11::worker() pthread_cond_wait = %d = %s>", res, strerror(res));
continue;
}
// res==0: triggered by signal, execute next cmd
RL0102_c *drive = selected_drive();
if (init_asserted) {
DEBUG("cmd %d ignored because of INIT.", function_code);
continue;
}
if ((busreg_CS->active_dati_flipflops & 0x80)) // CRDY must be cleared
ERROR("CRDY set, CS=%06o", busreg_CS->active_dati_flipflops);
// all commands: OPI, if drive powered off
clear_errors();
if (drive->state.value == RL0102_STATE_power_off) {
DEBUG("cmd %d ignored, drive powered off.", function_code);
do_operation_incomplete("worker: drive power off");
continue;
}
// inhibit command execution until previous seek complete (CRDY remains false)
bool seek_wait = false;
// DEBUG("AAA: drive->status_word = %06o", drive->status_word) ;
while ((drive->status_word & 0x07) == RL0102_STATE_seek) {
timeout.wait_ms(1);
if (!seek_wait) // suppress to much output
DEBUG("Start drive_busy_seeking. drive->status_word = %06o",
drive->status_word);
seek_wait = true;
}
if (seek_wait) {
// wait for "DRIVE ready" after seek: race condition between RL0102 and RL11
while ((busreg_CS->shared_register->value & 1) == 0)
;
// do_controller_status("seek busy ended") ;
DEBUG("End drive_busy_seeking: drive->status_word = %06o", drive->status_word);
}
// start execution of new command
// produce an interrupt on any transition to ready
switch (function_code) {
/* NOP, GETSTATUS have immediate INTR:
* handled fast in on_after_register_access()
*/
case CMD_WRITE_CHECK: // Write Check
DEBUG("cmd %d = Write Check", function_code);
change_state(RL11_STATE_RW_INIT);
// reads 1 sector into a separate buffer and compares data
break;
case CMD_SEEK:
// SEEK has immediate INTR: handled fast in on_after_register_access()
// but can be delayed if drive already seeking and cmd execution blocked
DEBUG("cmd %d = Seek (delayed)", function_code);
change_state(RL11_STATE_SEEK_INIT);
break;
case CMD_READ_HEADER: // Read sector header from disk
DEBUG("cmd %d = Read Header", function_code);
// read header if not locked-on track?
if (!drive->cmd_read_next_sector_header((uint16_t *) mpr_silo, 3)) {
}
set_MP_dati_silo(__func__);
do_command_done();
break;
case CMD_WRITE_DATA: // Write sector data from memory to disk
DEBUG("cmd %d = Write Data", function_code);
change_state(RL11_STATE_RW_INIT);
break;
case CMD_READ_DATA: // Read disk datao into memory
DEBUG("cmd %d = Read Data", function_code);
// sector address from DA, before seek must moved head to same track
change_state(RL11_STATE_RW_INIT);
break;
case CMD_READ_DATA_WITHOUT_HEADER_CHECK:
DEBUG("cmd %d = Read Data Without Check", function_code);
change_state(RL11_STATE_RW_INIT);
break;
default:
ERROR("RL11: invalid function code %u", function_code);
}
// execute command. CRDY is false, no new cmds are accepted
while (state != RL11_STATE_CONTROLLER_READY) {
// ZRLHB0 requires CSREADY within 400*20 usec (addr 015716)
// timeout.wait_ns(50000); // 50us
// log running operation need a timeout.wait_ns(). Internal in some states
// process current command states machines
// noop if machine terminates and state == RL11_STATE_CONTROLLER_READY
if (state & RL11_STATE_SEEK_MASK)
state_seek();
else if (state & RL11_STATE_RW_MASK)
state_readwrite();
}
}
assert(!pthread_mutex_unlock(&on_after_register_access_mutex));
}