github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/flash/spimock/spimock.go (about) 1 // Copyright 2021 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package spimock provides a fake SPI flash part for unit testing. This 6 // simulates an MX66L51235F. 7 package spimock 8 9 import ( 10 "bytes" 11 "fmt" 12 "io" 13 "os" 14 15 "github.com/mvdan/u-root-coreutils/pkg/flash/op" 16 "github.com/mvdan/u-root-coreutils/pkg/spidev" 17 "golang.org/x/sys/unix" 18 ) 19 20 // FakeSFDP is the SFDP from the MX66L51235F datasheet. 21 var FakeSFDP = []byte{ 22 // 0x00: SFDP Table 23 0x53, 0x46, 0x44, 0x50, 24 0x00, 0x01, 0x01, 0xff, 25 0x00, 0x00, 0x01, 0x09, 26 0x30, 0x00, 0x00, 0xff, 27 0xc2, 0x00, 0x01, 0x04, 28 0x60, 0x00, 0x00, 0xff, 29 // Padding 30 0xff, 0xff, 0xff, 0xff, 31 0xff, 0xff, 0xff, 0xff, 32 0xff, 0xff, 0xff, 0xff, 33 0xff, 0xff, 0xff, 0xff, 34 0xff, 0xff, 0xff, 0xff, 35 0xff, 0xff, 0xff, 0xff, 36 37 // 0x30: Table 0 38 0xe5, 0x20, 0xf3, 0xff, 39 0xff, 0xff, 0xff, 0x1f, 40 0x44, 0xeb, 0x08, 0x6b, 41 0x08, 0x3b, 0x04, 0xbb, 42 0xfe, 0xff, 0xff, 0xff, 43 0xff, 0xff, 0x00, 0xff, 44 0xff, 0xff, 0x44, 0xeb, 45 0x0c, 0x20, 0x0f, 0x52, 46 0x10, 0xd8, 0x00, 0xff, 47 // Padding 48 0xff, 0xff, 0xff, 0xff, 49 0xff, 0xff, 0xff, 0xff, 50 0xff, 0xff, 0xff, 0xff, 51 52 // 0x60: Table 1 53 0x00, 0x36, 0x00, 0x27, 54 0x9d, 0xf9, 0xc0, 0x64, 55 0x85, 0xcb, 0xff, 0xff, 56 0xff, 0xff, 0xff, 0xff, 57 } 58 59 // FakeSize is the size of the mocked flash chip. 60 const FakeSize = 64 * 1024 * 1024 61 62 // WriteWaitStates is the number WritePending is set to after a write. 63 const WriteWaitStates = 5 64 65 // MockSPI is an implementation of flash.SPI which records the transfers. 66 type MockSPI struct { 67 // Data contains fake contents of the flash chip. 68 Data []byte 69 // isMmap is set to true if Data is memory mapped to a file. 70 isMmap bool 71 // SFDPData contains fake SFDP. 72 SFDP []byte 73 74 SpeedHz uint32 75 76 // Is4BA is set to true if the chip is currently in 4-byte addressing 77 // mode. 78 Is4BA bool 79 // IsWriteEnabled is set to true if data can be written. 80 IsWriteEnabled bool 81 // WritePending is non-zero while a write is pending. It decreases on 82 // every read of the status register. 83 WritePending int 84 85 // Transfers is a recording of the transfers. 86 Transfers []spidev.Transfer 87 // ForceTransferError is returned by Transfer when set. 88 ForceTransferErr error 89 // ForceSetSpeedHzError is returned by SetSpeedHz when set. 90 ForceSetSpeedHzErr error 91 } 92 93 // New returns a new MockSPI in memory. 94 func New() *MockSPI { 95 return &MockSPI{ 96 Data: make([]byte, FakeSize), 97 SFDP: FakeSFDP, 98 } 99 } 100 101 // NewFromFile returns a new MockSPI which is backed by a file. If the file 102 // does not exist, it will be created. Ideally, the file's size should match 103 // FlashSize. 104 func NewFromFile(filename string) (*MockSPI, error) { 105 f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0o644) 106 if err != nil { 107 return nil, err 108 } 109 defer f.Close() 110 111 data, err := unix.Mmap(int(f.Fd()), 0, FakeSize, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED) 112 if err != nil { 113 return nil, err 114 } 115 116 return &MockSPI{ 117 Data: data, 118 isMmap: true, 119 SFDP: FakeSFDP, 120 }, nil 121 } 122 123 // Close closes the mock. 124 func (s *MockSPI) Close() error { 125 if s.isMmap { 126 return unix.Munmap(s.Data) 127 } 128 return nil 129 } 130 131 // tlen returns the length of a single transfers. 132 func tlen(t *spidev.Transfer) int { 133 if len(t.Tx) > len(t.Rx) { 134 return len(t.Tx) 135 } 136 return len(t.Rx) 137 } 138 139 // tx reads byte n from the transfers. 140 func tx(transfers []spidev.Transfer, n int) (byte, error) { 141 for i := range transfers { 142 l := tlen(&transfers[i]) 143 if n >= l { 144 n -= l 145 continue 146 } 147 if n >= len(transfers[i].Tx) { 148 return 0, nil 149 } 150 return transfers[i].Tx[n], nil 151 } 152 return 0, io.EOF 153 } 154 155 // rx writes byte n from the transfers. 156 func rx(transfers []spidev.Transfer, n int, val byte) error { 157 for i := range transfers { 158 l := tlen(&transfers[i]) 159 if n >= l { 160 n -= l 161 continue 162 } 163 if n >= len(transfers[i].Rx) { 164 return io.EOF 165 } 166 transfers[i].Rx[n] = val 167 return nil 168 } 169 return io.EOF 170 } 171 172 // address returns an address from the given tx offset in transfers and given 173 // addressing mode. The second return value is the 3 or 4 for the addressing mode. 174 func address(transfers []spidev.Transfer, off int, is4BA bool) (int64, int64) { 175 tx0 := func(n int) int64 { 176 b, _ := tx(transfers, n) 177 return int64(b) 178 } 179 180 if is4BA { 181 // Big-endian 182 return (tx0(off) << 24) | (tx0(off+1) << 16) | (tx0(off+2) << 8) | tx0(off+3), 4 183 } 184 // Big-endian 185 return (tx0(off) << 16) | (tx0(off+1) << 8) | tx0(off+2), 3 186 } 187 188 // Transfer implements flash.SPI. 189 func (s *MockSPI) Transfer(transfers []spidev.Transfer) error { 190 if s.ForceTransferErr != nil { 191 return s.ForceTransferErr 192 } 193 194 s.Transfers = append(s.Transfers, transfers...) 195 196 o, err := tx(transfers, 0) 197 if err != nil { 198 return err 199 } 200 switch o { 201 case op.PageProgram: 202 if !s.IsWriteEnabled { 203 break 204 } 205 addr, addrLen := address(transfers, 1, s.Is4BA) 206 // Copy each byte from tx to data with wrap-around within the page. 207 for i := 0; ; i++ { 208 b, err := tx(transfers, 1+int(addrLen)+i) 209 if err == io.EOF { 210 break 211 } 212 s.Data[addr&^255|(addr+int64(i))&255] &= b 213 } 214 s.IsWriteEnabled = false 215 s.WritePending = WriteWaitStates 216 case op.Read: 217 addr, addrLen := address(transfers, 1, s.Is4BA) 218 // Copy each byte from data to rx. 219 for i := int64(0); rx(transfers, int(1+addrLen+i), s.Data[addr+i]) == nil; i++ { 220 } 221 case op.WriteDisable: 222 s.IsWriteEnabled = false 223 case op.ReadStatus: 224 var statusReg uint8 225 if s.WritePending != 0 { 226 statusReg |= 1 227 } 228 if s.IsWriteEnabled { 229 statusReg |= 2 230 } 231 rx(transfers, 1, statusReg) 232 case op.WriteEnable: 233 s.IsWriteEnabled = true 234 case op.SectorErase: 235 if !s.IsWriteEnabled { 236 break 237 } 238 addr, _ := address(transfers, 1, s.Is4BA) 239 addr &= ^0xfff 240 copy(s.Data[addr:], bytes.Repeat([]byte{0xff}, 0x1000)) 241 s.IsWriteEnabled = false 242 case op.ReadSFDP: 243 // ReadSFDP is always 3-byte addressing. 244 addr, addrLen := address(transfers, 1, false) 245 // Copy each byte from sfdp to rx. 246 for i := int64(0); rx(transfers, int(1+addrLen+1+i), s.SFDP[addr+i]) == nil; i++ { 247 } 248 case op.ReadJEDECID: 249 rx(transfers, 1, 0xc2) 250 rx(transfers, 2, 0x20) 251 rx(transfers, 3, 0x1a) 252 case op.Enter4BA: 253 s.Is4BA = true 254 case op.BlockErase: 255 if !s.IsWriteEnabled { 256 break 257 } 258 addr, _ := address(transfers, 1, s.Is4BA) 259 addr &= ^0x7fff 260 copy(s.Data[addr:], bytes.Repeat([]byte{0xff}, 0x8000)) 261 s.IsWriteEnabled = false 262 case op.Exit4BA: 263 s.Is4BA = false 264 default: 265 return fmt.Errorf("unrecognized opcode %#02x", o) 266 } 267 268 return nil 269 } 270 271 // SetSpeedHz sets the SPI speed. The value set is recorded in the mock. 272 func (s *MockSPI) SetSpeedHz(hz uint32) error { 273 if s.ForceSetSpeedHzErr != nil { 274 return s.ForceSetSpeedHzErr 275 } 276 s.SpeedHz = hz 277 return nil 278 }