| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package virtual_device |
| |
| import ( |
| "errors" |
| "fmt" |
| "strings" |
| |
| "go.fuchsia.dev/fuchsia/tools/build" |
| "go.fuchsia.dev/fuchsia/tools/qemu" |
| fvdpb "go.fuchsia.dev/fuchsia/tools/virtual_device/proto" |
| ) |
| |
| // qemuTargets maps a common Fuchsia CPU architecture string to its QEMU equivalent. |
| // |
| // This should provide a mapping for each value allowed by `isValidArch`. |
| var qemuTargets = map[string]qemu.Target{ |
| "x64": qemu.TargetEnum.X86_64, |
| "arm64": qemu.TargetEnum.AArch64, |
| } |
| |
| // QEMUCommand sets options to run Fuchsia in QEMU on the given QEMUCommandBuilder. |
| // |
| // This returns an error if `Validate(fvd, images)` returns an error. |
| // |
| // This function is hermetic; It does not lookup or set the path to the QEMU binary, or |
| // interact with the filesystem or environment in anyway. These actions are left to the |
| // caller. |
| // |
| // The caller is free to set additional options on the builder before calling this function |
| // or after this function returns. |
| func QEMUCommand(b *qemu.QEMUCommandBuilder, fvd *fvdpb.VirtualDevice, images build.ImageManifest) error { |
| if len(images) == 0 { |
| return errors.New("image manifest cannot be empty") |
| } |
| if err := Validate(fvd, images); err != nil { |
| return err |
| } |
| |
| // Image paths. The previous call to Validate() ensures these exist in the manifest. |
| drive := "" |
| initrd := "" |
| kernel := "" |
| for _, image := range images { |
| switch { |
| case fvd.Kernel != "" && image.Name == fvd.Kernel && image.Type == "kernel": |
| kernel = image.Path |
| case image.Name == fvd.Initrd && image.Type == "zbi": |
| initrd = image.Path |
| case fvd.Drive != nil && image.Name == fvd.Drive.Image && image.Type == "blk": |
| drive = image.Path |
| } |
| } |
| if fvd.Drive != nil { |
| if fvd.Drive.IsFilename { |
| // Drive is a path instead of an image name. Assume the caller verified it exists. |
| drive = fvd.Drive.Image |
| } |
| b.AddVirtioBlkPciDrive(qemu.Drive{ |
| ID: fvd.Drive.Id, |
| Addr: fvd.Drive.PciAddress, |
| File: drive, |
| }) |
| } |
| |
| if kernel != "" { |
| b.SetKernel(kernel) |
| } |
| |
| b.SetInitrd(initrd) |
| |
| if fvd.Hw.Hci != "" { |
| b.AddHCI(qemu.HCI(fvd.Hw.Hci)) |
| } |
| |
| for _, drive := range fvd.Hw.Drives { |
| // TODO(kjharland): Refactor //tools/qemu to use a `Device` model and move this there. |
| switch drive.Device.Model { |
| case "usb-storage": |
| b.AddUSBDrive(qemu.Drive{ |
| ID: drive.Id, |
| File: drive.Image, |
| Addr: drive.PciAddress, |
| }) |
| case "virtio-blk-pci": |
| b.AddVirtioBlkPciDrive(qemu.Drive{ |
| ID: drive.Id, |
| File: drive.Image, |
| Addr: drive.PciAddress, |
| }) |
| default: |
| return fmt.Errorf("unimplemented drive model: %q", drive.Device.Model) |
| } |
| } |
| |
| b.SetCPUCount(int(fvd.Hw.CpuCount)) |
| |
| ramBytes, err := parseRAMBytes(fvd.Hw.Ram) |
| if err != nil { |
| return err |
| } |
| b.SetMemory(ramBytes) |
| |
| // The call to Validate() above guarantees the target is in this map. |
| target := qemuTargets[fvd.Hw.Arch] |
| b.SetTarget(target, fvd.Hw.EnableKvm) |
| |
| for _, d := range fvd.Hw.NetworkDevices { |
| // TODO(kjharland): Switch all tests to -nic, which is newer than -netdev. |
| netdev := qemu.Netdev{ |
| ID: d.Id, |
| Device: qemu.Device{Model: d.Device.Model}, |
| } |
| |
| for _, option := range d.Device.Options { |
| if opt := strings.SplitN(option, "=", 2); len(opt) == 2 { |
| netdev.Device.AddOption(opt[0], opt[1]) |
| } else { |
| return fmt.Errorf("invalid option: %q", option) |
| } |
| } |
| |
| switch d.Kind { |
| case "user": |
| netdev.User = &qemu.NetdevUser{} |
| case "tap": |
| netdev.Tap = &qemu.NetdevTap{Name: d.Id} |
| default: |
| // TODO(kjharland): Check this in Validate() |
| return fmt.Errorf("unimplemented network device model: %q", d.Device.Model) |
| } |
| b.AddNetwork(netdev) |
| } |
| |
| for _, arg := range fvd.KernelArgs { |
| b.AddKernelArg(arg) |
| } |
| |
| return nil |
| } |