///|
/// EEPROM (SII) register-level read via ESC registers 0x0502–0x0508.
/// Implements ETG.1500 #305 (EEPROM Read shall).
///
/// Protocol:
/// 1. Wait until SII control register bit 15 (busy) = 0
/// 2. Write word address to SII address register (0x0504, 2 bytes LE)
/// 3. Write read command to SII control register (0x0502): set bit 8 (read)
/// 4. Wait until SII control register bit 15 (busy) = 0
/// 5. Read 4 bytes from SII data register (0x0508)
///|
/// SII Control/Status register bits.
let sii_ctrl_busy : UInt = 0x8000U
///|
let sii_ctrl_nack : UInt = 0x2000U
///|
let sii_ctrl_r64 : UInt = 0x0040U
///|
let sii_ctrl_error_mask : UInt = 0x7800U
///|
let sii_ctrl_read_cmd : UInt = 0x0100U
///|
let sii_ctrl_nop_cmd : UInt = 0x0000U
///|
let sii_retry_limit : Int = 3
///|
let sii_cfg_force_pdi : Byte = b'\x02'
///|
let sii_cfg_master : Byte = b'\x00'
///|
let sii_cfg_pdi : Byte = b'\x01'
///|
/// EEPROM size word offset inside the fixed SII metadata block.
let sii_eeprom_size_word_offset : Int = 62
///|
/// Minimum bytes needed to read standard metadata and determine total EEPROM size.
let sii_initial_probe_bytes : Int = 128
///|
/// Maximum polls for SII busy flag before timeout.
let sii_max_polls : Int = 1000
///|
/// Read a single 32-bit (4-byte) word from a slave's EEPROM at the given
/// word address. Returns 4 bytes of EEPROM data.
pub fn[N : @hal.Nic] eeprom_read_word(
nic : N,
station : UInt,
word_address : UInt,
timeout : @hal.Duration,
) -> Bytes raise @hal.EcError {
eeprom_prepare_master(nic, station, timeout)
let data = eeprom_read_word_owned(nic, station, word_address, timeout)
ignore(eeprom_restore_pdi(nic, station, timeout) catch { _ => () })
data
}
///|
fn[N : @hal.Nic] eeprom_read_word_owned(
nic : N,
station : UInt,
word_address : UInt,
timeout : @hal.Duration,
) -> Bytes raise @hal.EcError {
let chunk = eeprom_read_chunk_owned(nic, station, word_address, timeout)
if chunk.length() < 4 {
raise @hal.EcError::SiiReadFailed("SII data chunk shorter than 4 bytes")
}
Bytes::makei(4, fn(i) { chunk[i] })
}
///|
fn[N : @hal.Nic] eeprom_read_chunk_owned(
nic : N,
station : UInt,
word_address : UInt,
timeout : @hal.Duration,
) -> Bytes raise @hal.EcError {
let ready = eeprom_wait_ready(nic, station, timeout)
if (ready & sii_ctrl_error_mask) != 0U {
eeprom_clear_errors(nic, station, timeout)
}
for _retry = 0; _retry < sii_retry_limit; _retry = _retry + 1 {
let ctrl_buf = Bytes::from_array([
(sii_ctrl_read_cmd & 0xFFU).to_byte(),
((sii_ctrl_read_cmd >> 8) & 0xFFU).to_byte(),
(word_address & 0xFFU).to_byte(),
((word_address >> 8) & 0xFFU).to_byte(),
b'\x00',
b'\x00',
])
let _ = write_register_fp(nic, station, reg_sii_control, ctrl_buf, timeout)
let status = eeprom_wait_ready_after_command(nic, station, timeout)
if (status & sii_ctrl_nack) != 0U {
continue
}
let read_len = if (status & sii_ctrl_r64) != 0U { 8 } else { 4 }
let pdu = read_register_fp(nic, station, reg_sii_data, read_len, timeout)
if pdu.data.length() < read_len {
raise @hal.EcError::SiiReadFailed(
"SII data register returned fewer than " +
read_len.to_string() +
" bytes",
)
}
return pdu.data
}
raise @hal.EcError::SiiReadFailed(
"SII read received NACK for word address " +
word_address.reinterpret_as_int().to_string(),
)
}
///|
fn[N : @hal.Nic] eeprom_prepare_master(
nic : N,
station : UInt,
timeout : @hal.Duration,
) -> Unit raise @hal.EcError {
eeprom_write_config(nic, station, sii_cfg_force_pdi, timeout)
eeprom_write_config(nic, station, sii_cfg_master, timeout)
}
///|
fn[N : @hal.Nic] eeprom_restore_pdi(
nic : N,
station : UInt,
timeout : @hal.Duration,
) -> Unit raise @hal.EcError {
eeprom_write_config(nic, station, sii_cfg_pdi, timeout)
}
///|
fn[N : @hal.Nic] eeprom_write_config(
nic : N,
station : UInt,
value : Byte,
timeout : @hal.Duration,
) -> Unit raise @hal.EcError {
let pdu = write_register_fp(
nic,
station,
reg_sii_config,
Bytes::from_array([value]),
timeout,
)
if pdu.wkc == 0U {
raise @hal.EcError::SiiReadFailed(
"failed to set EEPROM ownership for station " +
station.reinterpret_as_int().to_string(),
)
}
}
///|
/// Wait for the SII controller to become ready (not busy).
fn[N : @hal.Nic] eeprom_wait_ready(
nic : N,
station : UInt,
timeout : @hal.Duration,
) -> UInt raise @hal.EcError {
for _attempt = 0; _attempt < sii_max_polls; _attempt = _attempt + 1 {
let pdu = read_register_fp(nic, station, reg_sii_control, 2, timeout)
if pdu.data.length() >= 2 {
let status = pdu.data[0].to_uint() | (pdu.data[1].to_uint() << 8)
if (status & sii_ctrl_busy) == 0U {
return status
}
}
}
raise @hal.EcError::SiiReadFailed("SII busy timeout")
}
///|
fn[N : @hal.Nic] eeprom_wait_ready_after_command(
nic : N,
station : UInt,
timeout : @hal.Duration,
) -> UInt raise @hal.EcError {
let mut saw_busy = false
let mut last_status = 0U
for attempt = 0; attempt < sii_max_polls; attempt = attempt + 1 {
let pdu = read_register_fp(nic, station, reg_sii_control, 2, timeout)
if pdu.data.length() >= 2 {
let status = pdu.data[0].to_uint() | (pdu.data[1].to_uint() << 8)
last_status = status
if (status & sii_ctrl_busy) != 0U {
saw_busy = true
continue
}
if saw_busy || attempt > 0 {
return status
}
}
}
if last_status != 0U {
return last_status
}
raise @hal.EcError::SiiReadFailed("SII command completion timeout")
}
///|
fn[N : @hal.Nic] eeprom_clear_errors(
nic : N,
station : UInt,
timeout : @hal.Duration,
) -> Unit raise @hal.EcError {
let clear_buf = Bytes::from_array([
(sii_ctrl_nop_cmd & 0xFFU).to_byte(),
((sii_ctrl_nop_cmd >> 8) & 0xFFU).to_byte(),
])
let _ = write_register_fp(nic, station, reg_sii_control, clear_buf, timeout)
}
///|
/// Read a contiguous range of EEPROM data starting at a word address.
/// Returns `word_count * 4` bytes. Each read returns one 32-bit SII data window,
/// so consecutive reads advance by two 16-bit EEPROM words.
pub fn[N : @hal.Nic] eeprom_read(
nic : N,
station : UInt,
start_word : UInt,
word_count : Int,
timeout : @hal.Duration,
) -> Bytes raise @hal.EcError {
let target_length = word_count * 4
let buf = Buffer(size_hint=target_length)
let mut current_word = start_word
let mut remaining = target_length
eeprom_prepare_master(nic, station, timeout)
while remaining > 0 {
let chunk = eeprom_read_chunk_owned(nic, station, current_word, timeout)
let copy_len = if chunk.length() < remaining {
chunk.length()
} else {
remaining
}
for j = 0; j < copy_len; j = j + 1 {
buf.write_byte(chunk[j])
}
current_word = current_word + (if chunk.length() >= 8 { 4U } else { 2U })
remaining = remaining - copy_len
}
ignore(eeprom_restore_pdi(nic, station, timeout) catch { _ => () })
buf.to_bytes()
}
///|
fn decode_sii_total_bytes(probe : Bytes) -> Int? {
if probe.length() < sii_initial_probe_bytes {
return None
}
let byte_offset = sii_eeprom_size_word_offset * 2
let encoded = probe[byte_offset].to_uint() |
(probe[byte_offset + 1].to_uint() << 8)
Some(((encoded << 7) + 0x80U).reinterpret_as_int())
}
///|
fn required_sii_probe_word_count(initial_word_count : Int) -> Int {
let minimum_probe_words = sii_initial_probe_bytes / 4
if initial_word_count > minimum_probe_words {
initial_word_count
} else {
minimum_probe_words
}
}
///|
fn required_sii_total_word_count(
initial_eeprom : Bytes,
initial_word_count : Int,
) -> Int {
match decode_sii_total_bytes(initial_eeprom) {
Some(total_bytes) => {
let total_words = (total_bytes + 3) / 4
if total_words > initial_word_count {
total_words
} else {
initial_word_count
}
}
None => initial_word_count
}
}
///|
/// Read a complete SII EEPROM image using the fixed metadata block to expand
/// the initial probe to the EEPROM-declared total byte size.
pub fn[N : @hal.Nic] read_sii_bytes(
nic : N,
station : UInt,
initial_word_count : Int,
timeout : @hal.Duration,
) -> Bytes raise @hal.EcError {
let probe_word_count = required_sii_probe_word_count(initial_word_count)
let initial_eeprom = eeprom_read(nic, station, 0U, probe_word_count, timeout)
let total_word_count = required_sii_total_word_count(
initial_eeprom, probe_word_count,
)
if total_word_count <= probe_word_count {
initial_eeprom
} else {
eeprom_read(nic, station, 0U, total_word_count, timeout)
}
}
///|
/// Read a contiguous EEPROM range using auto-increment addressing.
/// This is useful during scan while slave positions are stable but configured
/// station addressing may not yet be reliable on all devices.
pub fn[N : @hal.Nic] eeprom_read_ap(
nic : N,
position : UInt,
start_word : UInt,
word_count : Int,
timeout : @hal.Duration,
) -> Bytes raise @hal.EcError {
let target_length = word_count * 4
let buf = Buffer(size_hint=target_length)
let mut current_word = start_word
let mut remaining = target_length
eeprom_prepare_master_ap(nic, position, timeout)
while remaining > 0 {
let chunk = eeprom_read_chunk_owned_ap(nic, position, current_word, timeout)
let copy_len = if chunk.length() < remaining {
chunk.length()
} else {
remaining
}
for j = 0; j < copy_len; j = j + 1 {
buf.write_byte(chunk[j])
}
current_word = current_word + (if chunk.length() >= 8 { 4U } else { 2U })
remaining = remaining - copy_len
}
ignore(eeprom_restore_pdi_ap(nic, position, timeout) catch { _ => () })
buf.to_bytes()
}
///|
/// Read a complete SII EEPROM image using auto-increment addressing.
pub fn[N : @hal.Nic] read_sii_bytes_ap(
nic : N,
position : UInt,
initial_word_count : Int,
timeout : @hal.Duration,
) -> Bytes raise @hal.EcError {
let probe_word_count = required_sii_probe_word_count(initial_word_count)
let initial_eeprom = eeprom_read_ap(
nic, position, 0U, probe_word_count, timeout,
)
let total_word_count = required_sii_total_word_count(
initial_eeprom, probe_word_count,
)
if total_word_count <= probe_word_count {
initial_eeprom
} else {
eeprom_read_ap(nic, position, 0U, total_word_count, timeout)
}
}
///|
fn[N : @hal.Nic] eeprom_read_chunk_owned_ap(
nic : N,
position : UInt,
word_address : UInt,
timeout : @hal.Duration,
) -> Bytes raise @hal.EcError {
let ready = eeprom_wait_ready_ap(nic, position, timeout)
if (ready & sii_ctrl_error_mask) != 0U {
eeprom_clear_errors_ap(nic, position, timeout)
}
for _retry = 0; _retry < sii_retry_limit; _retry = _retry + 1 {
let ctrl_buf = Bytes::from_array([
(sii_ctrl_read_cmd & 0xFFU).to_byte(),
((sii_ctrl_read_cmd >> 8) & 0xFFU).to_byte(),
(word_address & 0xFFU).to_byte(),
((word_address >> 8) & 0xFFU).to_byte(),
b'\x00',
b'\x00',
])
let _ = write_register_ap(nic, position, reg_sii_control, ctrl_buf, timeout)
let status = eeprom_wait_ready_after_command_ap(nic, position, timeout)
if (status & sii_ctrl_nack) != 0U {
continue
}
let read_len = if (status & sii_ctrl_r64) != 0U { 8 } else { 4 }
let pdu = read_register_ap(nic, position, reg_sii_data, read_len, timeout)
if pdu.data.length() < read_len {
raise @hal.EcError::SiiReadFailed(
"SII data register returned fewer than " +
read_len.to_string() +
" bytes",
)
}
return pdu.data
}
raise @hal.EcError::SiiReadFailed(
"SII AP read received NACK for position " +
position.reinterpret_as_int().to_string(),
)
}
///|
fn[N : @hal.Nic] eeprom_prepare_master_ap(
nic : N,
position : UInt,
timeout : @hal.Duration,
) -> Unit raise @hal.EcError {
eeprom_write_config_ap(nic, position, sii_cfg_force_pdi, timeout)
eeprom_write_config_ap(nic, position, sii_cfg_master, timeout)
}
///|
fn[N : @hal.Nic] eeprom_restore_pdi_ap(
nic : N,
position : UInt,
timeout : @hal.Duration,
) -> Unit raise @hal.EcError {
eeprom_write_config_ap(nic, position, sii_cfg_pdi, timeout)
}
///|
fn[N : @hal.Nic] eeprom_write_config_ap(
nic : N,
position : UInt,
value : Byte,
timeout : @hal.Duration,
) -> Unit raise @hal.EcError {
let pdu = write_register_ap(
nic,
position,
reg_sii_config,
Bytes::from_array([value]),
timeout,
)
if pdu.wkc == 0U {
raise @hal.EcError::SiiReadFailed(
"failed to set EEPROM ownership for position " +
position.reinterpret_as_int().to_string(),
)
}
}
///|
fn[N : @hal.Nic] eeprom_wait_ready_ap(
nic : N,
position : UInt,
timeout : @hal.Duration,
) -> UInt raise @hal.EcError {
for _attempt = 0; _attempt < sii_max_polls; _attempt = _attempt + 1 {
let pdu = read_register_ap(nic, position, reg_sii_control, 2, timeout)
if pdu.data.length() >= 2 {
let status = pdu.data[0].to_uint() | (pdu.data[1].to_uint() << 8)
if (status & sii_ctrl_busy) == 0U {
return status
}
}
}
raise @hal.EcError::SiiReadFailed("SII AP busy timeout")
}
///|
fn[N : @hal.Nic] eeprom_wait_ready_after_command_ap(
nic : N,
position : UInt,
timeout : @hal.Duration,
) -> UInt raise @hal.EcError {
let mut saw_busy = false
let mut last_status = 0U
for attempt = 0; attempt < sii_max_polls; attempt = attempt + 1 {
let pdu = read_register_ap(nic, position, reg_sii_control, 2, timeout)
if pdu.data.length() >= 2 {
let status = pdu.data[0].to_uint() | (pdu.data[1].to_uint() << 8)
last_status = status
if (status & sii_ctrl_busy) != 0U {
saw_busy = true
continue
}
if saw_busy || attempt > 0 {
return status
}
}
}
if last_status != 0U {
return last_status
}
raise @hal.EcError::SiiReadFailed("SII AP command completion timeout")
}
///|
fn[N : @hal.Nic] eeprom_clear_errors_ap(
nic : N,
position : UInt,
timeout : @hal.Duration,
) -> Unit raise @hal.EcError {
let clear_buf = Bytes::from_array([
(sii_ctrl_nop_cmd & 0xFFU).to_byte(),
((sii_ctrl_nop_cmd >> 8) & 0xFFU).to_byte(),
])
let _ = write_register_ap(nic, position, reg_sii_control, clear_buf, timeout)
}
///|
/// SII write command: bit 9 (write) + bit 0 (write enable).
let sii_ctrl_write_cmd : UInt = 0x0201U
///|
/// Write a single 16-bit word to a slave's EEPROM at the given word address.
/// Implements ETG.1500 #305 (EEPROM Write may).
///
/// Protocol (per SOEM pattern):
/// 1. Acquire Master ownership
/// 2. Write 16-bit data to SII data register (0x0508)
/// 3. Write command (0x0201) + address to SII control register (0x0502)
/// 4. Wait for busy flag to clear
/// 5. Restore PDI ownership
pub fn[N : @hal.Nic] eeprom_write_word(
nic : N,
station : UInt,
word_address : UInt,
data : UInt,
timeout : @hal.Duration,
) -> Unit raise @hal.EcError {
eeprom_prepare_master(nic, station, timeout)
eeprom_write_word_owned(nic, station, word_address, data, timeout)
ignore(eeprom_restore_pdi(nic, station, timeout) catch { _ => () })
}
///|
fn[N : @hal.Nic] eeprom_write_word_owned(
nic : N,
station : UInt,
word_address : UInt,
data : UInt,
timeout : @hal.Duration,
) -> Unit raise @hal.EcError {
let ready = eeprom_wait_ready(nic, station, timeout)
if (ready & sii_ctrl_error_mask) != 0U {
eeprom_clear_errors(nic, station, timeout)
}
for _retry = 0; _retry < sii_retry_limit; _retry = _retry + 1 {
// Write 16-bit data to SII data register BEFORE issuing the write command
let data_buf = Bytes::from_array([
(data & 0xFFU).to_byte(),
((data >> 8) & 0xFFU).to_byte(),
])
let _ = write_register_fp(nic, station, reg_sii_data, data_buf, timeout)
// Issue write command + address
let ctrl_buf = Bytes::from_array([
(sii_ctrl_write_cmd & 0xFFU).to_byte(),
((sii_ctrl_write_cmd >> 8) & 0xFFU).to_byte(),
(word_address & 0xFFU).to_byte(),
((word_address >> 8) & 0xFFU).to_byte(),
b'\x00',
b'\x00',
])
let _ = write_register_fp(nic, station, reg_sii_control, ctrl_buf, timeout)
let status = eeprom_wait_ready_after_command(nic, station, timeout)
if (status & sii_ctrl_nack) != 0U {
continue
}
return
}
raise @hal.EcError::SiiReadFailed(
"SII write received NACK for word address " +
word_address.reinterpret_as_int().to_string(),
)
}
///|
/// Write a single 16-bit word to a slave's EEPROM using auto-increment addressing.
pub fn[N : @hal.Nic] eeprom_write_word_ap(
nic : N,
position : UInt,
word_address : UInt,
data : UInt,
timeout : @hal.Duration,
) -> Unit raise @hal.EcError {
eeprom_prepare_master_ap(nic, position, timeout)
eeprom_write_word_owned_ap(nic, position, word_address, data, timeout)
ignore(eeprom_restore_pdi_ap(nic, position, timeout) catch { _ => () })
}
///|
fn[N : @hal.Nic] eeprom_write_word_owned_ap(
nic : N,
position : UInt,
word_address : UInt,
data : UInt,
timeout : @hal.Duration,
) -> Unit raise @hal.EcError {
let ready = eeprom_wait_ready_ap(nic, position, timeout)
if (ready & sii_ctrl_error_mask) != 0U {
eeprom_clear_errors_ap(nic, position, timeout)
}
for _retry = 0; _retry < sii_retry_limit; _retry = _retry + 1 {
let data_buf = Bytes::from_array([
(data & 0xFFU).to_byte(),
((data >> 8) & 0xFFU).to_byte(),
])
let _ = write_register_ap(nic, position, reg_sii_data, data_buf, timeout)
let ctrl_buf = Bytes::from_array([
(sii_ctrl_write_cmd & 0xFFU).to_byte(),
((sii_ctrl_write_cmd >> 8) & 0xFFU).to_byte(),
(word_address & 0xFFU).to_byte(),
((word_address >> 8) & 0xFFU).to_byte(),
b'\x00',
b'\x00',
])
let _ = write_register_ap(nic, position, reg_sii_control, ctrl_buf, timeout)
let status = eeprom_wait_ready_after_command_ap(nic, position, timeout)
if (status & sii_ctrl_nack) != 0U {
continue
}
return
}
raise @hal.EcError::SiiReadFailed(
"SII AP write received NACK for position " +
position.reinterpret_as_int().to_string(),
)
}