130 lines
3.5 KiB
Go
130 lines
3.5 KiB
Go
package proxmox
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
type PCIDevice struct {
|
|
ID string `json:"id"`
|
|
Class string `json:"class"`
|
|
Vendor string `json:"vendor"`
|
|
VendorName string `json:"vendor_name"`
|
|
Device string `json:"device"`
|
|
DeviceName string `json:"device_name"`
|
|
IOMMUGroup *int `json:"iommugroup"`
|
|
Driver string `json:"driver"`
|
|
}
|
|
|
|
type PCIMapping struct {
|
|
ID string `json:"id"`
|
|
Map []string `json:"map"`
|
|
}
|
|
|
|
type PendingOption struct {
|
|
Key string `json:"key"`
|
|
Value json.RawMessage `json:"value"`
|
|
Pending json.RawMessage `json:"pending"`
|
|
Delete json.RawMessage `json:"delete"`
|
|
}
|
|
|
|
func (c *Client) PCIDevices(ctx context.Context, node string) ([]PCIDevice, error) {
|
|
var items []PCIDevice
|
|
err := c.request(ctx, "GET", "/nodes/"+url.PathEscape(node)+"/hardware/pci?verbose=1", nil, &items)
|
|
return items, err
|
|
}
|
|
|
|
func (c *Client) PCIMappings(ctx context.Context) ([]PCIMapping, error) {
|
|
var items []PCIMapping
|
|
err := c.request(ctx, "GET", "/cluster/mapping/pci", nil, &items)
|
|
return items, err
|
|
}
|
|
|
|
func (c *Client) VMPending(ctx context.Context, node string, vmid int) ([]PendingOption, error) {
|
|
var items []PendingOption
|
|
err := c.request(ctx, "GET", fmt.Sprintf("/nodes/%s/qemu/%d/pending", url.PathEscape(node), vmid), nil, &items)
|
|
return items, err
|
|
}
|
|
|
|
var pciAddressPattern = regexp.MustCompile(`^(?:[0-9a-f]{4}:)?[0-9a-f]{2}:[0-1][0-9a-f](?:\.[0-7])?$`)
|
|
var hostPCIKeyPattern = regexp.MustCompile(`^hostpci[0-9]+$`)
|
|
|
|
// A functionless address selects every function in that PCI slot.
|
|
func NormalizePCIAddress(value string) (string, bool) {
|
|
value = strings.ToLower(strings.TrimSpace(value))
|
|
if !pciAddressPattern.MatchString(value) {
|
|
return "", false
|
|
}
|
|
if strings.Count(value, ":") == 1 {
|
|
value = "0000:" + value
|
|
}
|
|
return value, true
|
|
}
|
|
|
|
func PCIAddressMatches(selector, address string) bool {
|
|
selector, ok := NormalizePCIAddress(selector)
|
|
if !ok {
|
|
return false
|
|
}
|
|
address, ok = NormalizePCIAddress(address)
|
|
return ok && (selector == address || !strings.Contains(selector, ".") && strings.HasPrefix(address, selector+"."))
|
|
}
|
|
|
|
func (d PCIDevice) IsGPU() bool {
|
|
class := strings.TrimPrefix(strings.ToLower(d.Class), "0x")
|
|
return len(class) == 6 && strings.HasPrefix(class, "03")
|
|
}
|
|
|
|
type HostPCI struct {
|
|
Addresses []string
|
|
Mapping string
|
|
Mediated bool
|
|
}
|
|
|
|
func IsHostPCIKey(key string) bool { return hostPCIKeyPattern.MatchString(key) }
|
|
|
|
func PCIProperties(value string) (map[string]string, error) {
|
|
properties := map[string]string{}
|
|
for i, part := range strings.Split(value, ",") {
|
|
key, val, found := strings.Cut(strings.TrimSpace(part), "=")
|
|
if !found && i == 0 {
|
|
key, val = "host", key
|
|
} else if !found {
|
|
return nil, errors.New("invalid PCI property")
|
|
}
|
|
if _, duplicate := properties[key]; key == "" || val == "" || duplicate {
|
|
return nil, errors.New("invalid PCI property")
|
|
}
|
|
properties[key] = val
|
|
}
|
|
return properties, nil
|
|
}
|
|
|
|
func ParseHostPCI(value string) (HostPCI, error) {
|
|
var out HostPCI
|
|
properties, err := PCIProperties(value)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
out.Mapping = properties["mapping"]
|
|
out.Mediated = properties["mdev"] != ""
|
|
if (properties["host"] == "") == (out.Mapping == "") {
|
|
return out, errors.New("expected a host or mapping")
|
|
}
|
|
if properties["host"] != "" {
|
|
for _, address := range strings.Split(properties["host"], ";") {
|
|
normalized, ok := NormalizePCIAddress(address)
|
|
if !ok {
|
|
return out, errors.New("invalid PCI address")
|
|
}
|
|
out.Addresses = append(out.Addresses, normalized)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|