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  }