1// Copyright 2017 syzkaller project authors. All rights reserved. 2// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4// +build odroid 5 6package odroid 7 8// #cgo pkg-config: libusb-1.0 9// #include <linux/usb/ch9.h> 10// #include <linux/usb/ch11.h> 11// #include <libusb.h> 12import "C" 13 14import ( 15 "fmt" 16 "io" 17 "io/ioutil" 18 "os" 19 "os/exec" 20 "path/filepath" 21 "reflect" 22 "time" 23 "unsafe" 24 25 "github.com/google/syzkaller/pkg/config" 26 . "github.com/google/syzkaller/pkg/log" 27 "github.com/google/syzkaller/pkg/osutil" 28 "github.com/google/syzkaller/vm/vmimpl" 29) 30 31func init() { 32 vmimpl.Register("odroid", ctor) 33} 34 35type Config struct { 36 Host_Addr string // ip address of the host machine 37 Slave_Addr string // ip address of the Odroid board 38 Console string // console device name (e.g. "/dev/ttyUSB0") 39 Hub_Bus int // host USB bus number for the USB hub 40 Hub_Device int // host USB device number for the USB hub 41 Hub_Port int // port on the USB hub to which Odroid is connected 42} 43 44type Pool struct { 45 env *vmimpl.Env 46 cfg *Config 47} 48 49type instance struct { 50 cfg *Config 51 os string 52 sshkey string 53 closed chan bool 54 debug bool 55} 56 57func ctor(env *vmimpl.Env) (vmimpl.Pool, error) { 58 cfg := &Config{} 59 if err := config.LoadData(env.Config, cfg); err != nil { 60 return nil, fmt.Errorf("failed to parse odroid vm config: %v", err) 61 } 62 if cfg.Host_Addr == "" { 63 return nil, fmt.Errorf("config param host_addr is empty") 64 } 65 if cfg.Slave_Addr == "" { 66 return nil, fmt.Errorf("config param slave_addr is empty") 67 } 68 if cfg.Console == "" { 69 return nil, fmt.Errorf("config param console is empty") 70 } 71 if cfg.Hub_Bus == 0 { 72 return nil, fmt.Errorf("config param hub_bus is empty") 73 } 74 if cfg.Hub_Device == 0 { 75 return nil, fmt.Errorf("config param hub_device is empty") 76 } 77 if cfg.Hub_Port == 0 { 78 return nil, fmt.Errorf("config param hub_port is empty") 79 } 80 if !osutil.IxExist(cfg.Console) { 81 return nil, fmt.Errorf("console file '%v' does not exist", cfg.Console) 82 } 83 pool := &Pool{ 84 cfg: cfg, 85 env: env, 86 } 87 return pool, nil 88} 89 90func (pool *Pool) Count() int { 91 return 1 // no support for multiple Odroid devices yet 92} 93 94func (pool *Pool) Create(workdir string, index int) (vmimpl.Instance, error) { 95 inst := &instance{ 96 cfg: pool.cfg, 97 os: pool.env.OS, 98 sshkey: pool.env.Sshkey, 99 closed: make(chan bool), 100 debug: pool.env.Debug, 101 } 102 closeInst := inst 103 defer func() { 104 if closeInst != nil { 105 closeInst.Close() 106 } 107 }() 108 if err := inst.repair(); err != nil { 109 return nil, err 110 } 111 112 // Create working dir if doesn't exist. 113 inst.ssh("mkdir -p /data/") 114 115 // Remove temp files from previous runs. 116 inst.ssh("rm -rf /data/syzkaller-*") 117 118 closeInst = nil 119 return inst, nil 120} 121 122func (inst *instance) Forward(port int) (string, error) { 123 return fmt.Sprintf(inst.cfg.Host_Addr+":%v", port), nil 124} 125 126func (inst *instance) ssh(command string) ([]byte, error) { 127 if inst.debug { 128 Logf(0, "executing ssh %+v", command) 129 } 130 131 rpipe, wpipe, err := osutil.LongPipe() 132 if err != nil { 133 return nil, err 134 } 135 136 args := append(vmimpl.SSHArgs(inst.debug, inst.sshkey, 22), "root@"+inst.cfg.Slave_Addr, command) 137 if inst.debug { 138 Logf(0, "running command: ssh %#v", args) 139 } 140 cmd := osutil.Command("ssh", args...) 141 cmd.Stdout = wpipe 142 cmd.Stderr = wpipe 143 if err := cmd.Start(); err != nil { 144 wpipe.Close() 145 return nil, err 146 } 147 wpipe.Close() 148 149 done := make(chan bool) 150 go func() { 151 select { 152 case <-time.After(time.Minute): 153 if inst.debug { 154 Logf(0, "ssh hanged") 155 } 156 cmd.Process.Kill() 157 case <-done: 158 } 159 }() 160 if err := cmd.Wait(); err != nil { 161 close(done) 162 out, _ := ioutil.ReadAll(rpipe) 163 if inst.debug { 164 Logf(0, "ssh failed: %v\n%s", err, out) 165 } 166 return nil, fmt.Errorf("ssh %+v failed: %v\n%s", args, err, out) 167 } 168 close(done) 169 if inst.debug { 170 Logf(0, "ssh returned") 171 } 172 out, _ := ioutil.ReadAll(rpipe) 173 return out, nil 174} 175 176func switchPortPower(busNum, deviceNum, portNum int, power bool) error { 177 var context *C.libusb_context 178 if err := C.libusb_init(&context); err != 0 { 179 return fmt.Errorf("failed to init libusb: %v\n", err) 180 } 181 defer C.libusb_exit(context) 182 183 var rawList **C.libusb_device 184 numDevices := int(C.libusb_get_device_list(context, &rawList)) 185 if numDevices < 0 { 186 return fmt.Errorf("failed to init libusb: %v", numDevices) 187 } 188 defer C.libusb_free_device_list(rawList, 1) 189 190 var deviceList []*C.libusb_device 191 *(*reflect.SliceHeader)(unsafe.Pointer(&deviceList)) = reflect.SliceHeader{ 192 Data: uintptr(unsafe.Pointer(rawList)), 193 Len: numDevices, 194 Cap: numDevices, 195 } 196 197 var hub *C.libusb_device 198 for i := 0; i < numDevices; i++ { 199 var desc C.struct_libusb_device_descriptor 200 if err := C.libusb_get_device_descriptor(deviceList[i], &desc); err != 0 { 201 return fmt.Errorf("failed to get device descriptor: %v", err) 202 } 203 if desc.bDeviceClass != C.USB_CLASS_HUB { 204 continue 205 } 206 if C.libusb_get_bus_number(deviceList[i]) != C.uint8_t(busNum) { 207 continue 208 } 209 if C.libusb_get_device_address(deviceList[i]) != C.uint8_t(deviceNum) { 210 continue 211 } 212 hub = deviceList[i] 213 break 214 } 215 216 if hub == nil { 217 return fmt.Errorf("hub not found: bus: %v, device: %v", busNum, deviceNum) 218 } 219 220 var handle *C.libusb_device_handle 221 if err := C.libusb_open(hub, &handle); err != 0 { 222 return fmt.Errorf("failed to open usb device: %v", err) 223 } 224 225 request := C.uint8_t(C.USB_REQ_CLEAR_FEATURE) 226 if power { 227 request = C.uint8_t(C.USB_REQ_SET_FEATURE) 228 } 229 port := C.uint16_t(portNum) 230 timeout := C.uint(1000) 231 if err := C.libusb_control_transfer(handle, C.USB_RT_PORT, request, 232 C.USB_PORT_FEAT_POWER, port, nil, 0, timeout); err < 0 { 233 return fmt.Errorf("failed to send control message: %v\n", err) 234 } 235 236 return nil 237} 238 239func (inst *instance) repair() error { 240 // Try to shutdown gracefully. 241 Logf(1, "odroid: trying to ssh") 242 if err := inst.waitForSSH(10 * time.Second); err == nil { 243 Logf(1, "odroid: ssh succeeded, shutting down now") 244 inst.ssh("shutdown now") 245 if !vmimpl.SleepInterruptible(20 * time.Second) { 246 return fmt.Errorf("shutdown in progress") 247 } 248 } else { 249 Logf(1, "odroid: ssh failed") 250 } 251 252 // Hard reset by turning off and back on power on a hub port. 253 Logf(1, "odroid: hard reset, turning off power") 254 if err := switchPortPower(inst.cfg.Hub_Bus, inst.cfg.Hub_Device, inst.cfg.Hub_Port, false); err != nil { 255 return err 256 } 257 if !vmimpl.SleepInterruptible(5 * time.Second) { 258 return fmt.Errorf("shutdown in progress") 259 } 260 if err := switchPortPower(inst.cfg.Hub_Bus, inst.cfg.Hub_Device, inst.cfg.Hub_Port, true); err != nil { 261 return err 262 } 263 264 // Now wait for boot. 265 Logf(1, "odroid: power back on, waiting for boot") 266 if err := inst.waitForSSH(150 * time.Second); err != nil { 267 return err 268 } 269 270 Logf(1, "odroid: boot succeeded") 271 return nil 272} 273 274func (inst *instance) waitForSSH(timeout time.Duration) error { 275 return vmimpl.WaitForSSH(inst.debug, timeout, inst.cfg.Slave_Addr, inst.sshkey, "root", inst.os, 22) 276} 277 278func (inst *instance) Close() { 279 close(inst.closed) 280} 281 282func (inst *instance) Copy(hostSrc string) (string, error) { 283 basePath := "/data/" 284 vmDst := filepath.Join(basePath, filepath.Base(hostSrc)) 285 args := append(vmimpl.SCPArgs(inst.debug, inst.sshkey, 22), hostSrc, "root@"+inst.cfg.Slave_Addr+":"+vmDst) 286 cmd := osutil.Command("scp", args...) 287 if inst.debug { 288 Logf(0, "running command: scp %#v", args) 289 cmd.Stdout = os.Stdout 290 cmd.Stderr = os.Stdout 291 } 292 if err := cmd.Start(); err != nil { 293 return "", err 294 } 295 done := make(chan bool) 296 go func() { 297 select { 298 case <-time.After(3 * time.Minute): 299 cmd.Process.Kill() 300 case <-done: 301 } 302 }() 303 err := cmd.Wait() 304 close(done) 305 if err != nil { 306 return "", err 307 } 308 return vmDst, nil 309} 310 311func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command string) ( 312 <-chan []byte, <-chan error, error) { 313 tty, err := vmimpl.OpenConsole(inst.cfg.Console) 314 if err != nil { 315 return nil, nil, err 316 } 317 318 rpipe, wpipe, err := osutil.LongPipe() 319 if err != nil { 320 tty.Close() 321 return nil, nil, err 322 } 323 324 args := append(vmimpl.SSHArgs(inst.debug, inst.sshkey, 22), 325 "root@"+inst.cfg.Slave_Addr, "cd /data; "+command) 326 if inst.debug { 327 Logf(0, "running command: ssh %#v", args) 328 } 329 cmd := osutil.Command("ssh", args...) 330 cmd.Stdout = wpipe 331 cmd.Stderr = wpipe 332 if err := cmd.Start(); err != nil { 333 tty.Close() 334 rpipe.Close() 335 wpipe.Close() 336 return nil, nil, err 337 } 338 wpipe.Close() 339 340 var tee io.Writer 341 if inst.debug { 342 tee = os.Stdout 343 } 344 merger := vmimpl.NewOutputMerger(tee) 345 merger.Add("console", tty) 346 merger.Add("ssh", rpipe) 347 348 errc := make(chan error, 1) 349 signal := func(err error) { 350 select { 351 case errc <- err: 352 default: 353 } 354 } 355 356 go func() { 357 select { 358 case <-time.After(timeout): 359 signal(vmimpl.TimeoutErr) 360 case <-stop: 361 signal(vmimpl.TimeoutErr) 362 case <-inst.closed: 363 if inst.debug { 364 Logf(0, "instance closed") 365 } 366 signal(fmt.Errorf("instance closed")) 367 case err := <-merger.Err: 368 cmd.Process.Kill() 369 tty.Close() 370 merger.Wait() 371 if cmdErr := cmd.Wait(); cmdErr == nil { 372 // If the command exited successfully, we got EOF error from merger. 373 // But in this case no error has happened and the EOF is expected. 374 err = nil 375 } 376 signal(err) 377 return 378 } 379 cmd.Process.Kill() 380 tty.Close() 381 merger.Wait() 382 cmd.Wait() 383 }() 384 return merger.Output, errc, nil 385} 386