github.com/usbarmory/armory-boot@v0.0.0-20240307133412-208c66a380b9/cmd/armory-boot-usb/armory-boot-usb.go (about)

     1  // Copyright (c) WithSecure Corporation
     2  // https://foundry.withsecure.com
     3  //
     4  // Use of this source code is governed by the license
     5  // that can be found in the LICENSE file.
     6  
     7  // This tool implements a minimal set of the Serial Download Protocol (SDP),
     8  // used on NXP i.MX SoC application processors, to load an executable image
     9  // over USB.
    10  //
    11  // It implements a subset of the functionality also available in the following
    12  // tools:
    13  //  * https://github.com/NXPmicro/mfgtools
    14  //  * https://github.com/boundarydevices/imx_usb_loader
    15  
    16  package main
    17  
    18  import (
    19  	"errors"
    20  	"flag"
    21  	"io/ioutil"
    22  	"log"
    23  	"math/big"
    24  	"os"
    25  	"runtime"
    26  	"time"
    27  
    28  	"github.com/usbarmory/armory-boot/sdp"
    29  
    30  	"github.com/usbarmory/hid"
    31  )
    32  
    33  const (
    34  	// USB vendor ID for all supported devices
    35  	FreescaleVendorID = 0x15a2
    36  
    37  	// On-Chip RAM (OCRAM/iRAM) address for payload staging
    38  	iramOffset = 0x00910000
    39  )
    40  
    41  // SDP HID report IDs
    42  // (p323, 8.9.3.1 SDP commands, IMX6ULLRM).
    43  const (
    44  	H2D_COMMAND       = 1 // Command  - Host to Device
    45  	H2D_DATA          = 2 // Data     - Host to Device
    46  	D2H_RESPONSE      = 3 // Response - Device to Host
    47  	D2H_RESPONSE_LAST = 4 // Response - Device to Host
    48  )
    49  
    50  // This tool should work with all SoCs from the i.MX series capable of USB HID
    51  // based SDP, only tested devices are listed as supported, Pull Requests are
    52  // welcome to expand this set.
    53  var supportedDevices = map[uint16]string{
    54  	0x007d: "Freescale SemiConductor Inc  SE Blank 6UL",
    55  	0x0080: "Freescale SemiConductor Inc  SE Blank 6ULL",
    56  }
    57  
    58  type Config struct {
    59  	inf      *hid.DeviceInfo
    60  	dev      hid.Device
    61  	timeout  int
    62  	input    string
    63  	register string
    64  }
    65  
    66  var conf *Config
    67  
    68  func init() {
    69  	log.SetFlags(0)
    70  	log.SetOutput(os.Stdout)
    71  
    72  	conf = &Config{}
    73  
    74  	flag.IntVar(&conf.timeout, "t", 5, "timeout in seconds for command responses")
    75  	flag.StringVar(&conf.input, "i", "", "imx file")
    76  	flag.StringVar(&conf.register, "r", "0x021bc400", "read register")
    77  }
    78  
    79  // detect compatible devices in SDP mode
    80  func detect() (err error) {
    81  	devices, err := hid.Devices()
    82  
    83  	if err != nil {
    84  		return
    85  	}
    86  
    87  	for _, d := range devices {
    88  		if d.VendorID != FreescaleVendorID {
    89  			continue
    90  		}
    91  
    92  		if product, ok := supportedDevices[d.ProductID]; ok {
    93  			log.Printf("found device %04x:%04x %s", d.VendorID, d.ProductID, product)
    94  		} else {
    95  			continue
    96  		}
    97  
    98  		conf.inf = d
    99  		conf.dev, err = d.Open()
   100  
   101  		if err != nil {
   102  			return
   103  		}
   104  
   105  		break
   106  	}
   107  
   108  	if conf.dev == nil {
   109  		return errors.New("no device found, target missing or invalid permissions (forgot admin shell?)")
   110  	}
   111  
   112  	return
   113  }
   114  
   115  func sendHIDReport(reqID int, req []byte, resID int, n int) (res []byte, err error) {
   116  	p := append([]byte{byte(reqID)}, req...)
   117  
   118  	if err = conf.dev.Write(p); err != nil || resID < 0 {
   119  		return
   120  	}
   121  
   122  	if n > 0 {
   123  		conf.inf.InputReportLength = 1 + uint16(n)
   124  	}
   125  
   126  	timer := time.After(time.Duration(conf.timeout) * time.Second)
   127  
   128  	for {
   129  		select {
   130  		case res, ok := <-conf.dev.ReadCh():
   131  			if !ok {
   132  				return nil, errors.New("error reading response")
   133  			}
   134  
   135  			if len(res) > 0 && res[0] == byte(resID) {
   136  				return res[1:], nil
   137  			}
   138  		case <-timer:
   139  			return nil, errors.New("command timeout")
   140  		}
   141  	}
   142  }
   143  
   144  func readRegister(addr uint32, n int) {
   145  	r1 := sdp.BuildReadRegisterReport(addr, uint32(n))
   146  
   147  	log.Printf("reading %d bytes at %#x", n, addr)
   148  	res, err := sendHIDReport(H2D_COMMAND, r1, D2H_RESPONSE_LAST, n)
   149  
   150  	if err != nil {
   151  		log.Fatal(err)
   152  	}
   153  
   154  	log.Printf("%#.8x: %x", addr, res)
   155  }
   156  
   157  func dcdWrite(dcd []byte, addr uint32) (err error) {
   158  	r1, r2 := sdp.BuildDCDWriteReport(dcd, addr)
   159  
   160  	_, err = sendHIDReport(H2D_COMMAND, r1, -1, -1)
   161  
   162  	if err != nil {
   163  		return
   164  	}
   165  
   166  	_, err = sendHIDReport(H2D_DATA, r2, D2H_RESPONSE_LAST, -1)
   167  
   168  	return
   169  }
   170  
   171  func fileWrite(imx []byte, addr uint32) (err error) {
   172  	r1, r2 := sdp.BuildFileWriteReport(imx, addr)
   173  
   174  	_, err = sendHIDReport(H2D_COMMAND, r1, -1, -1)
   175  
   176  	if err != nil {
   177  		return
   178  	}
   179  
   180  	resID := -1
   181  	timer := time.After(time.Duration(conf.timeout) * time.Second)
   182  
   183  	for i, r := range r2 {
   184  		if i == len(r2)-1 {
   185  			resID = D2H_RESPONSE_LAST
   186  		}
   187  	send:
   188  		_, err = sendHIDReport(H2D_DATA, r, resID, -1)
   189  
   190  		if err != nil && runtime.GOOS == "darwin" && err.Error() == "hid: general error" {
   191  			// On macOS access contention with the OS causes
   192  			// errors, as a workaround we retry from the transfer
   193  			// that got caught up.
   194  			select {
   195  			case <-timer:
   196  				return
   197  			default:
   198  				off := uint32(i) * 1024
   199  				r1 := &sdp.SDP{
   200  					CommandType: sdp.WriteFile,
   201  					Address:     addr + off,
   202  					DataCount:   uint32(len(imx)) - off,
   203  				}
   204  
   205  				if _, err = sendHIDReport(H2D_COMMAND, r1.Bytes(), -1, -1); err != nil {
   206  					return
   207  				}
   208  
   209  				goto send
   210  			}
   211  		}
   212  
   213  		if err != nil {
   214  			break
   215  		}
   216  	}
   217  
   218  	return
   219  }
   220  
   221  func jumpAddress(addr uint32) (err error) {
   222  	r1 := sdp.BuildJumpAddressReport(addr)
   223  	_, err = sendHIDReport(H2D_COMMAND, r1, -1, -1)
   224  
   225  	return
   226  }
   227  
   228  func writeAndJump(input string) {
   229  	log.Printf("parsing %s", input)
   230  	imx, err := ioutil.ReadFile(input)
   231  
   232  	if err != nil {
   233  		log.Fatal(err)
   234  	}
   235  
   236  	ivt, err := sdp.ParseIVT(imx)
   237  
   238  	if err != nil {
   239  		log.Fatalf("IVT parsing error: %v", err)
   240  	}
   241  
   242  	dcd, err := sdp.ParseDCD(imx, ivt)
   243  
   244  	if err != nil {
   245  		log.Fatalf("DCD parsing error: %v", err)
   246  	}
   247  
   248  	log.Printf("loading DCD at %#08x (%d bytes)", iramOffset, len(dcd))
   249  	err = dcdWrite(dcd, iramOffset)
   250  
   251  	if err != nil {
   252  		log.Fatal(err)
   253  	}
   254  
   255  	log.Printf("loading imx to %#08x (%d bytes)", ivt.Self, len(imx))
   256  	err = fileWrite(imx, ivt.Self)
   257  
   258  	if err != nil {
   259  		log.Fatal(err)
   260  	}
   261  
   262  	log.Printf("jumping to %#08x", ivt.Self)
   263  	err = jumpAddress(ivt.Self)
   264  
   265  	if err != nil {
   266  		log.Fatal(err)
   267  	}
   268  
   269  	log.Printf("serial download complete")
   270  }
   271  
   272  func main() {
   273  	var err error
   274  
   275  	flag.Parse()
   276  
   277  	if err = detect(); err != nil {
   278  		log.Fatal(err)
   279  	}
   280  
   281  	switch {
   282  	case len(conf.input) > 0:
   283  		writeAndJump(conf.input)
   284  	case len(conf.register) > 0:
   285  		addr := new(big.Int)
   286  		addr.SetString(conf.register, 0)
   287  		readRegister(uint32(addr.Int64()), 4)
   288  	default:
   289  		flag.PrintDefaults()
   290  	}
   291  }