1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/* composite.rs
 *
 * Developed by Tim Walls <tim.walls@snowgoons.com>
 * Copyright (c) All Rights Reserved, Tim Walls
 */
//! Composite serial PROM devices (i.e. devices that are made up of
//! multiple discrete chips with different I2C device IDs that make up
//! a single larger storage volume.)

// Imports ===================================================================
use core::marker::PhantomData;
use core::mem::MaybeUninit;
use avr_oxide::devices::serialbus::{SerialBusClient, UsesSerialBusClient};
use avr_oxide::hal::generic::twi::TwiAddr;
use avrox_storage::{RandomRead, RandomWrite, SAddr, SSize};
use avrox_storage::random::{AccessHint, Storage};
use avr_oxide::OxideResult::Ok;

// Declarations ==============================================================
/// A CompositeSerProm is a serial PROM device made up of multiple
/// subsidiary devices, each with a different I2C bus address, that are
/// stitched together to appear as a single device.
pub struct CompositeSerProm<const ELEMENTS: usize, const I2C_MASK: u8, const I2C_SHIFT: u8, BC, ELEMENT>
where
  BC: SerialBusClient,
  ELEMENT: UsesSerialBusClient<BC>
{
  elements: [ELEMENT; ELEMENTS],
  phantom: PhantomData<BC>
}

// Code ======================================================================
impl<const ELEMENTS: usize, const I2C_MASK: u8, const I2C_SHIFT: u8, BC, ELEMENT>
  UsesSerialBusClient<BC> for CompositeSerProm<ELEMENTS,I2C_MASK,I2C_SHIFT,BC,ELEMENT>
where
  BC: SerialBusClient,
  ELEMENT: UsesSerialBusClient<BC>
{
  fn using_client(base_client: BC) -> Self {
    let mut elements: [MaybeUninit<ELEMENT>; ELEMENTS] = unsafe {
      MaybeUninit::uninit().assume_init()
    };


    let mut base_addr = base_client.get_bus_addr();

    for element in &mut elements {
      let element_client = base_client.clone_with_bus_addr(base_addr);
      element.write(ELEMENT::using_client(element_client));

      // Now calculate the address for the next element
      let next_element_addr = ((base_addr.write_addr() & I2C_MASK) >> I2C_SHIFT) + 1;

      base_addr = TwiAddr::addr((base_addr.write_addr() & (!I2C_MASK)) | (next_element_addr << I2C_SHIFT));
    }

    CompositeSerProm {
      elements: unsafe { core::mem::transmute_copy::<_,[ELEMENT; ELEMENTS]>(&elements) },
      phantom: PhantomData::default()
    }
  }
}

impl<const ELEMENTS: usize, const I2C_MASK: u8, const I2C_SHIFT: u8, BC, ELEMENT>
  RandomRead for CompositeSerProm<ELEMENTS,I2C_MASK,I2C_SHIFT,BC,ELEMENT>
  where
    BC: SerialBusClient,
    ELEMENT: UsesSerialBusClient<BC> + RandomRead + Storage
{
  fn read_at_hinted(&self, addr: SAddr, buf: &mut [u8], _hint: AccessHint) -> avr_oxide::io::Result<usize> {
    let device = (addr / ELEMENT::addressable_bytes()) as usize;
    let device_addr = addr % ELEMENT::addressable_bytes();

    self.elements[device].read_at(device_addr, buf)
  }
}

impl<const ELEMENTS: usize, const I2C_MASK: u8, const I2C_SHIFT: u8, BC, ELEMENT>
RandomWrite for CompositeSerProm<ELEMENTS,I2C_MASK,I2C_SHIFT,BC,ELEMENT>
  where
    BC: SerialBusClient,
    ELEMENT: UsesSerialBusClient<BC> + RandomWrite + Storage
{
  fn write_at_hinted(&mut self, addr: SAddr, buf: &[u8], _hint: AccessHint) -> avr_oxide::io::Result<usize> {
    let device = (addr / ELEMENT::addressable_bytes()) as usize;
    let device_addr = addr % ELEMENT::addressable_bytes();

    self.elements[device].write_at(device_addr, buf)
  }

  fn flush(&mut self) -> avr_oxide::io::Result<()> {
    for device in &mut self.elements {
      device.flush()?;
    }
    Ok(())
  }
}

impl<const ELEMENTS: usize, const I2C_MASK: u8, const I2C_SHIFT: u8, BC, ELEMENT>
Storage for CompositeSerProm<ELEMENTS,I2C_MASK,I2C_SHIFT,BC,ELEMENT>
  where
    BC: SerialBusClient,
    ELEMENT: UsesSerialBusClient<BC> + RandomWrite + Storage
{
  const ADDRESSABLE_BYTES: SSize = (ELEMENTS as SSize) * ELEMENT::ADDRESSABLE_BYTES;
}


// Tests =====================================================================
#[cfg(test)]
pub mod tests {
  use avrox_storage::serprom::composite::CompositeSerProm;
  use avrox_storage::serprom::generic::SerPromD8A16be;
  use avrox_storage::serprom::generic::dummy::DummyPromBusClient;
  use avr_oxide::devices::serialbus::UsesSerialBusClient;
  use avrox_storage::{RandomRead,RandomWrite};


  pub type TestBusClient = DummyPromBusClient<0x50,65_536>;
  pub type TestCompositeProm = CompositeSerProm<2,0b00000010,1,TestBusClient,SerPromD8A16be<65_536,256,TestBusClient>>;

  #[test]
  fn test_basic_prom_operations() {
    let mut test_prom = TestCompositeProm::using_client(DummyPromBusClient::new());
    let mut buffer = [ 0x00u8, 0x00u8, 0x00u8, 0x00u8 ];

    // Check basic write/read
    test_prom.write_all_at(0x0000u32, &[ 0x01u8, 0x02u8, 0x03u8, 0x04u8 ]).unwrap();
    test_prom.read_full_at(0x0000u32, &mut buffer).unwrap();
    assert_eq!(buffer, [ 0x01u8, 0x02u8, 0x03u8, 0x04u8 ]);

    // Check basic write/read at a different address
    test_prom.write_all_at(0x0123u32, &[ 0x01u8, 0x02u8, 0x03u8, 0x04u8 ]).unwrap();
    test_prom.read_full_at(0x0123u32, &mut buffer).unwrap();
    assert_eq!(buffer, [ 0x01u8, 0x02u8, 0x03u8, 0x04u8 ]);

    // Check basic write/read at the second chip
    test_prom.write_all_at(0x10123u32, &[ 0x08u8, 0x07u8, 0x06u8, 0x05u8 ]).unwrap();
    test_prom.read_full_at(0x10123u32, &mut buffer).unwrap();
    assert_eq!(buffer, [ 0x08u8, 0x07u8, 0x06u8, 0x05u8 ]);

    // Make sure that didn't actually write to the first chip
    test_prom.read_full_at(0x0123u32, &mut buffer).unwrap();
    assert_eq!(buffer, [ 0x01u8, 0x02u8, 0x03u8, 0x04u8 ]);
  }

  #[test]
  fn test_prom_chip_boundaries() {
    let mut test_prom = TestCompositeProm::using_client(DummyPromBusClient::new());
    let mut buffer = [ 0x00u8, 0x00u8, 0x00u8, 0x00u8 ];

    // The following write should wrap over the end of a chip boundary,
    // so if the PROM driver implementation is wrong we'll feck it up
    test_prom.write_all_at(0x00fffeu32, &[ 0xde, 0xad, 0xbe, 0xef ]).unwrap();

    // This read should 'appear' to work, because the read will also wrap
    // at the boundary
    test_prom.read_full_at(0x00fffeu32, &mut buffer).unwrap();
    assert_eq!(buffer, [ 0xde, 0xad, 0xbe, 0xef ]);

    // But we must check it's actually right...
    test_prom.read_full_at(0x10000u32, &mut buffer).unwrap();
    assert_eq!(buffer, [ 0xbe, 0xef, 0xff, 0xff ]);
  }
}