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 }