diff --git a/qmp.go b/qmp.go index 5cc19af0e..ec2910444 100644 --- a/qmp.go +++ b/qmp.go @@ -675,3 +675,19 @@ func (q *QMP) ExecuteDeviceDel(ctx context.Context, devID string) error { } return q.executeCommand(ctx, "device_del", args, filter) } + +// ExecutePCIDeviceAdd is the PCI version of ExecuteDeviceAdd. This function can be used +// to hot plug PCI devices on PCI(E) bridges, unlike ExecuteDeviceAdd this function receive the +// device address on its parent bus. bus is optional. +func (q *QMP) ExecutePCIDeviceAdd(ctx context.Context, blockdevID, devID, driver, addr, bus string) error { + args := map[string]interface{}{ + "id": devID, + "driver": driver, + "drive": blockdevID, + "addr": addr, + } + if bus != "" { + args["bus"] = bus + } + return q.executeCommand(ctx, "device_add", args, nil) +} diff --git a/qmp_test.go b/qmp_test.go index 5b63b1707..e8072431c 100644 --- a/qmp_test.go +++ b/qmp_test.go @@ -785,3 +785,28 @@ func TestQMPLostLoop(t *testing.T) { t.Error("Expected executeQMPCapabilities to fail") } } + +// Checks that PCI devices are correctly added using device_add. +// +// We start a QMPLoop, send the device_add command and stop the loop. +// +// The device_add command should be correctly sent and the QMP loop should +// exit gracefully. +func TestQMPPCIDeviceAdd(t *testing.T) { + connectedCh := make(chan *QMPVersion) + disconnectedCh := make(chan struct{}) + buf := newQMPTestCommandBuffer(t) + buf.AddCommand("device_add", nil, "return", nil) + cfg := QMPConfig{Logger: qmpTestLogger{}} + q := startQMPLoop(buf, cfg, connectedCh, disconnectedCh) + checkVersion(t, connectedCh) + blockdevID := fmt.Sprintf("drive_%s", testutil.VolumeUUID) + devID := fmt.Sprintf("device_%s", testutil.VolumeUUID) + err := q.ExecutePCIDeviceAdd(context.Background(), blockdevID, devID, + "virtio-blk-pci", "0x1", "") + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + q.Shutdown() + <-disconnectedCh +}