libvirt/src/conf/capabilities.c
Michal Privoznik 87a43a907f lib: Use g_clear_pointer() more
This change was generated using the following spatch:

  @ rule1 @
  expression a;
  identifier f;
  @@
    <...
  - f(*a);
    ... when != a;
  - *a = NULL;
  + g_clear_pointer(a, f);
    ...>

  @ rule2 @
  expression a;
  identifier f;
  @@
    <...
  - f(a);
    ... when != a;
  - a = NULL;
  + g_clear_pointer(&a, f);
    ...>

Then, I left some of the changes out, like tools/nss/ (which
doesn't link with glib) and put back a comment in
qemuBlockJobProcessEventCompletedActiveCommit() which coccinelle
decided to remove (I have no idea why).

Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Ján Tomko <jtomko@redhat.com>
2022-02-08 08:42:07 +01:00

2286 lines
68 KiB
C

/*
* capabilities.c: hypervisor capabilities
*
* Copyright (C) 2006-2015 Red Hat, Inc.
* Copyright (C) 2006-2008 Daniel P. Berrange
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <unistd.h>
#include "capabilities.h"
#include "cpu_conf.h"
#include "domain_conf.h"
#include "storage_conf.h"
#include "viralloc.h"
#include "virarch.h"
#include "virbuffer.h"
#include "virerror.h"
#include "virfile.h"
#include "virhostcpu.h"
#include "virhostmem.h"
#include "virlog.h"
#include "virnuma.h"
#include "virstring.h"
#include "virtypedparam.h"
#include "viruuid.h"
#include "virenum.h"
#include "virutil.h"
#define VIR_FROM_THIS VIR_FROM_CAPABILITIES
#define SYSFS_SYSTEM_PATH "/sys/devices/system"
VIR_LOG_INIT("conf.capabilities");
VIR_ENUM_DECL(virCapsHostPMTarget);
VIR_ENUM_IMPL(virCapsHostPMTarget,
VIR_NODE_SUSPEND_TARGET_LAST,
"suspend_mem", "suspend_disk", "suspend_hybrid",
);
static virClass *virCapsClass;
static void virCapsDispose(void *obj);
static int virCapabilitiesOnceInit(void)
{
if (!VIR_CLASS_NEW(virCaps, virClassForObject()))
return -1;
return 0;
}
VIR_ONCE_GLOBAL_INIT(virCapabilities);
/**
* virCapabilitiesNew:
* @hostarch: host machine architecture
* @offlineMigrate: true if offline migration is available
* @liveMigrate: true if live migration is available
*
* Allocate a new capabilities object
*/
virCaps *
virCapabilitiesNew(virArch hostarch,
bool offlineMigrate,
bool liveMigrate)
{
virCaps *caps;
if (virCapabilitiesInitialize() < 0)
return NULL;
if (!(caps = virObjectNew(virCapsClass)))
return NULL;
caps->host.arch = hostarch;
caps->host.offlineMigrate = offlineMigrate;
caps->host.liveMigrate = liveMigrate;
return caps;
}
void
virCapabilitiesClearHostNUMACellCPUTopology(virCapsHostNUMACellCPU *cpus,
size_t ncpus)
{
size_t i;
if (!cpus)
return;
for (i = 0; i < ncpus; i++)
g_clear_pointer(&cpus[i].siblings, virBitmapFree);
}
static void
virCapabilitiesFreeHostNUMACell(virCapsHostNUMACell *cell)
{
if (cell == NULL)
return;
virCapabilitiesClearHostNUMACellCPUTopology(cell->cpus, cell->ncpus);
g_free(cell->cpus);
g_free(cell->distances);
g_free(cell->pageinfo);
if (cell->caches)
g_array_unref(cell->caches);
g_free(cell);
}
static void
virCapabilitiesFreeGuestMachine(virCapsGuestMachine *machine)
{
if (machine == NULL)
return;
g_free(machine->name);
g_free(machine->canonical);
g_free(machine);
}
static void
virCapabilitiesFreeGuestDomain(virCapsGuestDomain *dom)
{
size_t i;
if (dom == NULL)
return;
g_free(dom->info.emulator);
g_free(dom->info.loader);
for (i = 0; i < dom->info.nmachines; i++)
virCapabilitiesFreeGuestMachine(dom->info.machines[i]);
g_free(dom->info.machines);
g_free(dom);
}
void
virCapabilitiesFreeGuest(virCapsGuest *guest)
{
size_t i;
if (guest == NULL)
return;
g_free(guest->arch.defaultInfo.emulator);
g_free(guest->arch.defaultInfo.loader);
for (i = 0; i < guest->arch.defaultInfo.nmachines; i++)
virCapabilitiesFreeGuestMachine(guest->arch.defaultInfo.machines[i]);
g_free(guest->arch.defaultInfo.machines);
for (i = 0; i < guest->arch.ndomains; i++)
virCapabilitiesFreeGuestDomain(guest->arch.domains[i]);
g_free(guest->arch.domains);
g_free(guest);
}
static void
virCapabilitiesFreeStoragePool(virCapsStoragePool *pool)
{
if (!pool)
return;
g_free(pool);
}
void
virCapabilitiesHostNUMAUnref(virCapsHostNUMA *caps)
{
if (!caps)
return;
if (g_atomic_int_dec_and_test(&caps->refs)) {
g_ptr_array_unref(caps->cells);
if (caps->interconnects)
g_array_unref(caps->interconnects);
g_free(caps);
}
}
void
virCapabilitiesHostNUMARef(virCapsHostNUMA *caps)
{
g_atomic_int_inc(&caps->refs);
}
static void
virCapsHostMemBWNodeFree(virCapsHostMemBWNode *ptr)
{
if (!ptr)
return;
virBitmapFree(ptr->cpus);
g_free(ptr);
}
static void
virCapabilitiesClearSecModel(virCapsHostSecModel *secmodel)
{
size_t i;
for (i = 0; i < secmodel->nlabels; i++) {
VIR_FREE(secmodel->labels[i].type);
VIR_FREE(secmodel->labels[i].label);
}
VIR_FREE(secmodel->labels);
VIR_FREE(secmodel->model);
VIR_FREE(secmodel->doi);
}
static void
virCapsDispose(void *object)
{
virCaps *caps = object;
size_t i;
for (i = 0; i < caps->npools; i++)
virCapabilitiesFreeStoragePool(caps->pools[i]);
g_free(caps->pools);
for (i = 0; i < caps->nguests; i++)
virCapabilitiesFreeGuest(caps->guests[i]);
g_free(caps->guests);
for (i = 0; i < caps->host.nfeatures; i++)
g_free(caps->host.features[i]);
g_free(caps->host.features);
if (caps->host.numa)
virCapabilitiesHostNUMAUnref(caps->host.numa);
for (i = 0; i < caps->host.nmigrateTrans; i++)
g_free(caps->host.migrateTrans[i]);
g_free(caps->host.migrateTrans);
for (i = 0; i < caps->host.nsecModels; i++)
virCapabilitiesClearSecModel(&caps->host.secModels[i]);
g_free(caps->host.secModels);
for (i = 0; i < caps->host.cache.nbanks; i++)
virCapsHostCacheBankFree(caps->host.cache.banks[i]);
virResctrlInfoMonFree(caps->host.cache.monitor);
g_free(caps->host.cache.banks);
for (i = 0; i < caps->host.memBW.nnodes; i++)
virCapsHostMemBWNodeFree(caps->host.memBW.nodes[i]);
virResctrlInfoMonFree(caps->host.memBW.monitor);
g_free(caps->host.memBW.nodes);
g_free(caps->host.netprefix);
g_free(caps->host.pagesSize);
virCPUDefFree(caps->host.cpu);
virObjectUnref(caps->host.resctrl);
}
/**
* virCapabilitiesAddHostFeature:
* @caps: capabilities to extend
* @name: name of new feature
*
* Registers a new host CPU feature, eg 'pae', or 'vmx'
*/
int
virCapabilitiesAddHostFeature(virCaps *caps,
const char *name)
{
VIR_RESIZE_N(caps->host.features, caps->host.nfeatures_max,
caps->host.nfeatures, 1);
caps->host.features[caps->host.nfeatures] = g_strdup(name);
caps->host.nfeatures++;
return 0;
}
/**
* virCapabilitiesAddHostMigrateTransport:
* @caps: capabilities to extend
* @name: name of migration transport
*
* Registers a new domain migration transport URI
*/
int
virCapabilitiesAddHostMigrateTransport(virCaps *caps,
const char *name)
{
VIR_RESIZE_N(caps->host.migrateTrans, caps->host.nmigrateTrans_max,
caps->host.nmigrateTrans, 1);
caps->host.migrateTrans[caps->host.nmigrateTrans] = g_strdup(name);
caps->host.nmigrateTrans++;
return 0;
}
/**
* virCapabilitiesSetNetPrefix:
* @caps: capabilities to extend
* @name: prefix for host generated network interfaces
*
* Registers the prefix that is used for generated network interfaces
*/
int
virCapabilitiesSetNetPrefix(virCaps *caps,
const char *prefix)
{
caps->host.netprefix = g_strdup(prefix);
return 0;
}
/**
* virCapabilitiesHostNUMAAddCell:
* @caps: capabilities to extend
* @num: ID number of NUMA cell
* @mem: Total size of memory in the NUMA node (in KiB)
* @ncpus: number of CPUs in cell
* @cpus: array of CPU definition structures
* @ndistances: number of sibling NUMA nodes
* @distances: NUMA distances to other nodes
* @npageinfo: number of pages at node @num
* @pageinfo: info on each single memory page
* @caches: info on memory side caches
*
* Registers a new NUMA cell for a host, passing in a array of
* CPU IDs belonging to the cell, distances to other NUMA nodes
* and info on hugepages on the node.
*
* All pointers are stolen.
*/
void
virCapabilitiesHostNUMAAddCell(virCapsHostNUMA *caps,
int num,
unsigned long long mem,
int ncpus,
virCapsHostNUMACellCPU **cpus,
int ndistances,
virNumaDistance **distances,
int npageinfo,
virCapsHostNUMACellPageInfo **pageinfo,
GArray **caches)
{
virCapsHostNUMACell *cell = g_new0(virCapsHostNUMACell, 1);
cell->num = num;
cell->mem = mem;
if (cpus) {
cell->ncpus = ncpus;
cell->cpus = g_steal_pointer(cpus);
}
if (distances) {
cell->ndistances = ndistances;
cell->distances = g_steal_pointer(distances);
}
if (pageinfo) {
cell->npageinfo = npageinfo;
cell->pageinfo = g_steal_pointer(pageinfo);
}
if (caches) {
cell->caches = g_steal_pointer(caches);
}
g_ptr_array_add(caps->cells, cell);
}
/**
* virCapabilitiesAllocMachines:
* @machines: machine variants for emulator ('pc', or 'isapc', etc)
* @nmachines: number of machine variants for emulator
*
* Allocate a table of virCapsGuestMachine *from the supplied table
* of machine names.
*/
virCapsGuestMachine **
virCapabilitiesAllocMachines(const char *const *names, int nnames)
{
virCapsGuestMachine **machines;
size_t i;
machines = g_new0(virCapsGuestMachine *, nnames);
for (i = 0; i < nnames; i++) {
machines[i] = g_new0(virCapsGuestMachine, 1);
machines[i]->name = g_strdup(names[i]);
}
return machines;
}
/**
* virCapabilitiesFreeMachines:
* @machines: table of vircapsGuestMachinePtr
*
* Free a table of virCapsGuestMachine *
*/
void
virCapabilitiesFreeMachines(virCapsGuestMachine **machines,
int nmachines)
{
size_t i;
if (!machines)
return;
for (i = 0; i < nmachines && machines[i]; i++) {
g_clear_pointer(&machines[i], virCapabilitiesFreeGuestMachine);
}
g_free(machines);
}
/**
* virCapabilitiesAddGuest:
* @caps: capabilities to extend
* @ostype: guest operating system type, of enum VIR_DOMAIN_OSTYPE
* @arch: guest CPU architecture
* @wordsize: number of bits in CPU word
* @emulator: path to default device emulator for arch/ostype
* @loader: path to default BIOS loader for arch/ostype
* @nmachines: number of machine variants for emulator
* @machines: machine variants for emulator ('pc', or 'isapc', etc)
*
* Registers a new guest operating system. This should be
* followed by registration of at least one domain for
* running the guest
*/
virCapsGuest *
virCapabilitiesAddGuest(virCaps *caps,
int ostype,
virArch arch,
const char *emulator,
const char *loader,
int nmachines,
virCapsGuestMachine **machines)
{
virCapsGuest *guest;
guest = g_new0(virCapsGuest, 1);
guest->ostype = ostype;
guest->arch.id = arch;
guest->arch.wordsize = virArchGetWordSize(arch);
guest->arch.defaultInfo.emulator = g_strdup(emulator);
guest->arch.defaultInfo.loader = g_strdup(loader);
VIR_RESIZE_N(caps->guests, caps->nguests_max, caps->nguests, 1);
caps->guests[caps->nguests++] = guest;
if (nmachines) {
guest->arch.defaultInfo.nmachines = nmachines;
guest->arch.defaultInfo.machines = machines;
}
return guest;
}
/**
* virCapabilitiesAddGuestDomain:
* @guest: guest to support
* @hvtype: hypervisor type ('xen', 'qemu', 'kvm')
* @emulator: specialized device emulator for domain
* @loader: specialized BIOS loader for domain
* @nmachines: number of machine variants for emulator
* @machines: specialized machine variants for emulator
*
* Registers a virtual domain capable of running a
* guest operating system
*/
virCapsGuestDomain *
virCapabilitiesAddGuestDomain(virCapsGuest *guest,
int hvtype,
const char *emulator,
const char *loader,
int nmachines,
virCapsGuestMachine **machines)
{
virCapsGuestDomain *dom;
dom = g_new0(virCapsGuestDomain, 1);
dom->type = hvtype;
dom->info.emulator = g_strdup(emulator);
dom->info.loader = g_strdup(loader);
VIR_RESIZE_N(guest->arch.domains, guest->arch.ndomains_max,
guest->arch.ndomains, 1);
guest->arch.domains[guest->arch.ndomains] = dom;
guest->arch.ndomains++;
if (nmachines) {
dom->info.nmachines = nmachines;
dom->info.machines = machines;
}
return dom;
}
struct virCapsGuestFeatureInfo {
const char *name;
bool togglesRequired;
};
static const struct virCapsGuestFeatureInfo virCapsGuestFeatureInfos[VIR_CAPS_GUEST_FEATURE_TYPE_LAST] = {
[VIR_CAPS_GUEST_FEATURE_TYPE_PAE] = { "pae", false },
[VIR_CAPS_GUEST_FEATURE_TYPE_NONPAE] = { "nonpae", false },
[VIR_CAPS_GUEST_FEATURE_TYPE_IA64_BE] = { "ia64_be", false },
[VIR_CAPS_GUEST_FEATURE_TYPE_ACPI] = { "acpi", true },
[VIR_CAPS_GUEST_FEATURE_TYPE_APIC] = { "apic", true },
[VIR_CAPS_GUEST_FEATURE_TYPE_CPUSELECTION] = { "cpuselection", false },
[VIR_CAPS_GUEST_FEATURE_TYPE_DEVICEBOOT] = { "deviceboot", false },
[VIR_CAPS_GUEST_FEATURE_TYPE_DISKSNAPSHOT] = { "disksnapshot", true },
[VIR_CAPS_GUEST_FEATURE_TYPE_HAP] = { "hap", true },
};
static void
virCapabilitiesAddGuestFeatureInternal(virCapsGuest *guest,
virCapsGuestFeatureType feature,
bool defaultOn,
bool toggle)
{
guest->features[feature].present = true;
if (virCapsGuestFeatureInfos[feature].togglesRequired) {
guest->features[feature].defaultOn = virTristateSwitchFromBool(defaultOn);
guest->features[feature].toggle = virTristateBoolFromBool(toggle);
}
}
/**
* virCapabilitiesAddGuestFeature:
* @guest: guest to associate feature with
* @feature: feature to add
*
* Registers a feature for a guest domain.
*/
void
virCapabilitiesAddGuestFeature(virCapsGuest *guest,
virCapsGuestFeatureType feature)
{
virCapabilitiesAddGuestFeatureInternal(guest, feature, false, false);
}
/**
* virCapabilitiesAddGuestFeatureWithToggle:
* @guest: guest to associate feature with
* @feature: feature to add
* @defaultOn: true if it defaults to on
* @toggle: true if its state can be toggled
*
* Registers a feature with toggles for a guest domain.
*/
void
virCapabilitiesAddGuestFeatureWithToggle(virCapsGuest *guest,
virCapsGuestFeatureType feature,
bool defaultOn,
bool toggle)
{
virCapabilitiesAddGuestFeatureInternal(guest, feature, defaultOn, toggle);
}
/**
* virCapabilitiesHostSecModelAddBaseLabel
* @secmodel: Security model to add a base label for
* @type: virtualization type
* @label: base label
*
* Returns non-zero on error.
*/
extern int
virCapabilitiesHostSecModelAddBaseLabel(virCapsHostSecModel *secmodel,
const char *type,
const char *label)
{
if (type == NULL || label == NULL)
return -1;
VIR_EXPAND_N(secmodel->labels, secmodel->nlabels, 1);
secmodel->labels[secmodel->nlabels - 1].type = g_strdup(type);
secmodel->labels[secmodel->nlabels - 1].label = g_strdup(label);
return 0;
}
static virCapsDomainData *
virCapabilitiesDomainDataLookupInternal(virCaps *caps,
int ostype,
virArch arch,
virDomainVirtType domaintype,
const char *emulator,
const char *machinetype)
{
virCapsGuest *foundguest = NULL;
virCapsGuestDomain *founddomain = NULL;
virCapsGuestMachine *foundmachine = NULL;
virCapsDomainData *ret = NULL;
size_t i, j, k;
VIR_DEBUG("Lookup ostype=%d arch=%d domaintype=%d emulator=%s machine=%s",
ostype, arch, domaintype, NULLSTR(emulator), NULLSTR(machinetype));
for (i = 0; i < caps->nguests; i++) {
virCapsGuest *guest = caps->guests[i];
if (ostype != -1 && guest->ostype != ostype) {
VIR_DEBUG("Skip os type want=%d vs got=%d", ostype, guest->ostype);
continue;
}
VIR_DEBUG("Match os type %d", ostype);
if ((arch != VIR_ARCH_NONE) && (guest->arch.id != arch)) {
VIR_DEBUG("Skip arch want=%d vs got=%d", arch, guest->arch.id);
continue;
}
VIR_DEBUG("Match arch %d", arch);
for (j = 0; j < guest->arch.ndomains; j++) {
virCapsGuestDomain *domain = guest->arch.domains[j];
virCapsGuestMachine **machinelist;
int nmachines;
const char *check_emulator = NULL;
if (domaintype != VIR_DOMAIN_VIRT_NONE &&
(domain->type != domaintype)) {
VIR_DEBUG("Skip domain type want=%d vs got=%d", domaintype, domain->type);
continue;
}
VIR_DEBUG("Match domain type %d", domaintype);
check_emulator = domain->info.emulator;
if (!check_emulator)
check_emulator = guest->arch.defaultInfo.emulator;
if (emulator && STRNEQ_NULLABLE(check_emulator, emulator)) {
VIR_DEBUG("Skip emulator got=%s vs want=%s",
emulator, NULLSTR(check_emulator));
continue;
}
VIR_DEBUG("Match emulator %s", NULLSTR(emulator));
if (domain->info.nmachines) {
nmachines = domain->info.nmachines;
machinelist = domain->info.machines;
} else {
nmachines = guest->arch.defaultInfo.nmachines;
machinelist = guest->arch.defaultInfo.machines;
}
for (k = 0; k < nmachines; k++) {
virCapsGuestMachine *machine = machinelist[k];
if (machinetype &&
STRNEQ(machine->name, machinetype) &&
STRNEQ_NULLABLE(machine->canonical, machinetype)) {
VIR_DEBUG("Skip machine type want=%s vs got=%s got=%s",
machinetype, machine->name, NULLSTR(machine->canonical));
continue;
}
VIR_DEBUG("Match machine type machine %s", NULLSTR(machinetype));
foundmachine = machine;
break;
}
if (!foundmachine && nmachines)
continue;
founddomain = domain;
break;
}
if (!founddomain)
continue;
foundguest = guest;
break;
}
/* XXX check default_emulator, see how it uses this */
if (!foundguest) {
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
if (ostype)
virBufferAsprintf(&buf, "ostype=%s ",
virDomainOSTypeToString(ostype));
if (arch)
virBufferAsprintf(&buf, "arch=%s ", virArchToString(arch));
if (domaintype > VIR_DOMAIN_VIRT_NONE)
virBufferAsprintf(&buf, "domaintype=%s ",
virDomainVirtTypeToString(domaintype));
if (emulator)
virBufferEscapeString(&buf, "emulator=%s ", emulator);
if (machinetype)
virBufferEscapeString(&buf, "machine=%s ", machinetype);
if (virBufferCurrentContent(&buf) &&
!virBufferCurrentContent(&buf)[0])
virBufferAsprintf(&buf, "%s", _("any configuration"));
virReportError(VIR_ERR_INVALID_ARG,
_("could not find capabilities for %s"),
virBufferCurrentContent(&buf));
return ret;
}
ret = g_new0(virCapsDomainData, 1);
ret->ostype = foundguest->ostype;
ret->arch = foundguest->arch.id;
if (founddomain) {
ret->domaintype = founddomain->type;
ret->emulator = founddomain->info.emulator;
}
if (!ret->emulator)
ret->emulator = foundguest->arch.defaultInfo.emulator;
if (foundmachine)
ret->machinetype = foundmachine->name;
return ret;
}
/**
* virCapabilitiesDomainDataLookup:
* @caps: capabilities to query
* @ostype: guest operating system type, of enum VIR_DOMAIN_OSTYPE
* @arch: Architecture to search for
* @domaintype: domain type to search for, of enum virDomainVirtType
* @emulator: Emulator path to search for
* @machinetype: Machine type to search for
*
* Search capabilities for the passed values, and if found return
* virCapabilitiesDomainDataLookup filled in with the default values
*/
virCapsDomainData *
virCapabilitiesDomainDataLookup(virCaps *caps,
int ostype,
virArch arch,
int domaintype,
const char *emulator,
const char *machinetype)
{
virCapsDomainData *ret;
if (arch == VIR_ARCH_NONE) {
/* Prefer host arch if its available */
ret = virCapabilitiesDomainDataLookupInternal(caps, ostype,
caps->host.arch,
domaintype,
emulator, machinetype);
if (ret)
return ret;
}
return virCapabilitiesDomainDataLookupInternal(caps, ostype,
arch, domaintype,
emulator, machinetype);
}
bool
virCapabilitiesDomainSupported(virCaps *caps,
int ostype,
virArch arch,
int virttype)
{
g_autofree virCapsDomainData *capsdata = NULL;
capsdata = virCapabilitiesDomainDataLookup(caps, ostype,
arch,
virttype,
NULL, NULL);
return capsdata != NULL;
}
int
virCapabilitiesAddStoragePool(virCaps *caps,
int poolType)
{
virCapsStoragePool *pool;
pool = g_new0(virCapsStoragePool, 1);
pool->type = poolType;
VIR_RESIZE_N(caps->pools, caps->npools_max, caps->npools, 1);
caps->pools[caps->npools++] = pool;
return 0;
}
static int
virCapsHostNUMACellCPUFormat(virBuffer *buf,
const virCapsHostNUMACellCPU *cpus,
int ncpus)
{
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
size_t j;
virBufferAsprintf(&attrBuf, " num='%d'", ncpus);
for (j = 0; j < ncpus; j++) {
virBufferAsprintf(&childBuf, "<cpu id='%d'", cpus[j].id);
if (cpus[j].siblings) {
g_autofree char *siblings = NULL;
if (!(siblings = virBitmapFormat(cpus[j].siblings)))
return -1;
virBufferAsprintf(&childBuf,
" socket_id='%d' die_id='%d' core_id='%d' siblings='%s'",
cpus[j].socket_id,
cpus[j].die_id,
cpus[j].core_id,
siblings);
}
virBufferAddLit(&childBuf, "/>\n");
}
virXMLFormatElement(buf, "cpus", &attrBuf, &childBuf);
return 0;
}
static int
virCapabilitiesHostNUMAFormat(virBuffer *buf,
virCapsHostNUMA *caps)
{
size_t i;
if (!caps)
return 0;
virBufferAddLit(buf, "<topology>\n");
virBufferAdjustIndent(buf, 2);
virBufferAsprintf(buf, "<cells num='%d'>\n", caps->cells->len);
virBufferAdjustIndent(buf, 2);
for (i = 0; i < caps->cells->len; i++) {
virCapsHostNUMACell *cell = g_ptr_array_index(caps->cells, i);
size_t j;
virBufferAsprintf(buf, "<cell id='%d'>\n", cell->num);
virBufferAdjustIndent(buf, 2);
/* Print out the numacell memory total if it is available */
if (cell->mem)
virBufferAsprintf(buf, "<memory unit='KiB'>%llu</memory>\n",
cell->mem);
for (j = 0; j < cell->npageinfo; j++) {
virBufferAsprintf(buf, "<pages unit='KiB' size='%u'>%llu</pages>\n",
cell->pageinfo[j].size,
cell->pageinfo[j].avail);
}
virNumaDistanceFormat(buf, cell->distances, cell->ndistances);
if (cell->caches) {
virNumaCache *caches = &g_array_index(cell->caches, virNumaCache, 0);
virNumaCacheFormat(buf, caches, cell->caches->len);
}
if (virCapsHostNUMACellCPUFormat(buf, cell->cpus, cell->ncpus) < 0)
return -1;
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "</cell>\n");
}
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "</cells>\n");
if (caps->interconnects) {
const virNumaInterconnect *interconnects;
interconnects = &g_array_index(caps->interconnects, virNumaInterconnect, 0);
virNumaInterconnectFormat(buf, interconnects, caps->interconnects->len);
}
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "</topology>\n");
return 0;
}
static int
virCapabilitiesFormatResctrlMonitor(virBuffer *buf,
virResctrlInfoMon *monitor)
{
size_t i = 0;
g_auto(virBuffer) childrenBuf = VIR_BUFFER_INIT_CHILD(buf);
/* monitor not supported, no capability */
if (!monitor)
return 0;
/* no feature found in monitor means no capability, return */
if (monitor->nfeatures == 0)
return 0;
virBufferAddLit(buf, "<monitor ");
/* CMT might not enabled, if enabled show related attributes. */
if (monitor->type == VIR_RESCTRL_MONITOR_TYPE_CACHE)
virBufferAsprintf(buf,
"level='%u' reuseThreshold='%u' ",
monitor->cache_level,
monitor->cache_reuse_threshold);
virBufferAsprintf(buf,
"maxMonitors='%u'>\n",
monitor->max_monitor);
for (i = 0; i < monitor->nfeatures; i++) {
virBufferAsprintf(&childrenBuf,
"<feature name='%s'/>\n",
monitor->features[i]);
}
virBufferAddBuffer(buf, &childrenBuf);
virBufferAddLit(buf, "</monitor>\n");
return 0;
}
static int
virCapabilitiesFormatCaches(virBuffer *buf,
virCapsHostCache *cache)
{
size_t i = 0;
size_t j = 0;
if (!cache->nbanks && !cache->monitor)
return 0;
virBufferAddLit(buf, "<cache>\n");
virBufferAdjustIndent(buf, 2);
for (i = 0; i < cache->nbanks; i++) {
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
g_auto(virBuffer) childrenBuf = VIR_BUFFER_INIT_CHILD(buf);
virCapsHostCacheBank *bank = cache->banks[i];
g_autofree char *cpus_str = virBitmapFormat(bank->cpus);
const char *unit = NULL;
unsigned long long short_size = virFormatIntPretty(bank->size, &unit);
if (!cpus_str)
return -1;
/*
* Let's just *hope* the size is aligned to KiBs so that it does not
* bite is back in the future
*/
virBufferAsprintf(&attrBuf,
" id='%u' level='%u' type='%s' "
"size='%llu' unit='%s' cpus='%s'",
bank->id, bank->level,
virCacheTypeToString(bank->type),
short_size, unit, cpus_str);
for (j = 0; j < bank->ncontrols; j++) {
const char *min_unit;
virResctrlInfoPerCache *controls = bank->controls[j];
unsigned long long gran_short_size = controls->granularity;
unsigned long long min_short_size = controls->min;
gran_short_size = virFormatIntPretty(gran_short_size, &unit);
min_short_size = virFormatIntPretty(min_short_size, &min_unit);
/* Only use the smaller unit if they are different */
if (min_short_size) {
unsigned long long gran_div;
unsigned long long min_div;
gran_div = controls->granularity / gran_short_size;
min_div = controls->min / min_short_size;
if (min_div > gran_div) {
min_short_size *= min_div / gran_div;
} else if (min_div < gran_div) {
unit = min_unit;
gran_short_size *= gran_div / min_div;
}
}
virBufferAsprintf(&childrenBuf,
"<control granularity='%llu'",
gran_short_size);
if (min_short_size)
virBufferAsprintf(&childrenBuf, " min='%llu'", min_short_size);
virBufferAsprintf(&childrenBuf,
" unit='%s' type='%s' maxAllocs='%u'/>\n",
unit,
virCacheTypeToString(controls->scope),
controls->max_allocation);
}
virXMLFormatElement(buf, "bank", &attrBuf, &childrenBuf);
}
if (virCapabilitiesFormatResctrlMonitor(buf, cache->monitor) < 0)
return -1;
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "</cache>\n");
return 0;
}
static int
virCapabilitiesFormatMemoryBandwidth(virBuffer *buf,
virCapsHostMemBW *memBW)
{
size_t i = 0;
if (!memBW->nnodes && !memBW->monitor)
return 0;
virBufferAddLit(buf, "<memory_bandwidth>\n");
virBufferAdjustIndent(buf, 2);
for (i = 0; i < memBW->nnodes; i++) {
g_auto(virBuffer) attrBuf = VIR_BUFFER_INITIALIZER;
g_auto(virBuffer) childrenBuf = VIR_BUFFER_INIT_CHILD(buf);
virCapsHostMemBWNode *node = memBW->nodes[i];
virResctrlInfoMemBWPerNode *control = &node->control;
g_autofree char *cpus_str = virBitmapFormat(node->cpus);
if (!cpus_str)
return -1;
virBufferAsprintf(&attrBuf,
" id='%u' cpus='%s'",
node->id, cpus_str);
virBufferAsprintf(&childrenBuf,
"<control granularity='%u' min='%u' "
"maxAllocs='%u'/>\n",
control->granularity, control->min,
control->max_allocation);
virXMLFormatElement(buf, "node", &attrBuf, &childrenBuf);
}
if (virCapabilitiesFormatResctrlMonitor(buf, memBW->monitor) < 0)
return -1;
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "</memory_bandwidth>\n");
return 0;
}
static int
virCapabilitiesFormatHostXML(virCapsHost *host,
virBuffer *buf)
{
size_t i, j;
char host_uuid[VIR_UUID_STRING_BUFLEN];
/* The lack of some data means we have nothing
* minimally to format, so just return. */
if (!virUUIDIsValid(host->host_uuid) &&
!host->arch && !host->powerMgmt && !host->iommu)
return 0;
virBufferAddLit(buf, "<host>\n");
virBufferAdjustIndent(buf, 2);
if (virUUIDIsValid(host->host_uuid)) {
virUUIDFormat(host->host_uuid, host_uuid);
virBufferAsprintf(buf, "<uuid>%s</uuid>\n", host_uuid);
}
virBufferAddLit(buf, "<cpu>\n");
virBufferAdjustIndent(buf, 2);
if (host->arch)
virBufferAsprintf(buf, "<arch>%s</arch>\n",
virArchToString(host->arch));
if (host->nfeatures) {
virBufferAddLit(buf, "<features>\n");
virBufferAdjustIndent(buf, 2);
for (i = 0; i < host->nfeatures; i++) {
virBufferAsprintf(buf, "<%s/>\n",
host->features[i]);
}
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "</features>\n");
}
virCPUDefFormatBuf(buf, host->cpu);
for (i = 0; i < host->nPagesSize; i++) {
virBufferAsprintf(buf, "<pages unit='KiB' size='%u'/>\n",
host->pagesSize[i]);
}
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "</cpu>\n");
/* The PM query was successful. */
if (host->powerMgmt) {
/* The host supports some PM features. */
unsigned int pm = host->powerMgmt;
virBufferAddLit(buf, "<power_management>\n");
virBufferAdjustIndent(buf, 2);
while (pm) {
int bit = __builtin_ffs(pm) - 1;
virBufferAsprintf(buf, "<%s/>\n",
virCapsHostPMTargetTypeToString(bit));
pm &= ~(1U << bit);
}
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "</power_management>\n");
} else {
/* The host does not support any PM feature. */
virBufferAddLit(buf, "<power_management/>\n");
}
virBufferAsprintf(buf, "<iommu support='%s'/>\n",
host->iommu ? "yes" : "no");
if (host->offlineMigrate) {
virBufferAddLit(buf, "<migration_features>\n");
virBufferAdjustIndent(buf, 2);
if (host->liveMigrate)
virBufferAddLit(buf, "<live/>\n");
if (host->nmigrateTrans) {
virBufferAddLit(buf, "<uri_transports>\n");
virBufferAdjustIndent(buf, 2);
for (i = 0; i < host->nmigrateTrans; i++) {
virBufferAsprintf(buf, "<uri_transport>%s</uri_transport>\n",
host->migrateTrans[i]);
}
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "</uri_transports>\n");
}
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "</migration_features>\n");
}
if (host->netprefix)
virBufferAsprintf(buf, "<netprefix>%s</netprefix>\n",
host->netprefix);
if (virCapabilitiesHostNUMAFormat(buf, host->numa) < 0)
return -1;
if (virCapabilitiesFormatCaches(buf, &host->cache) < 0)
return -1;
if (virCapabilitiesFormatMemoryBandwidth(buf, &host->memBW) < 0)
return -1;
for (i = 0; i < host->nsecModels; i++) {
virBufferAddLit(buf, "<secmodel>\n");
virBufferAdjustIndent(buf, 2);
virBufferAsprintf(buf, "<model>%s</model>\n",
host->secModels[i].model);
virBufferAsprintf(buf, "<doi>%s</doi>\n",
host->secModels[i].doi);
for (j = 0; j < host->secModels[i].nlabels; j++) {
virBufferAsprintf(buf, "<baselabel type='%s'>%s</baselabel>\n",
host->secModels[i].labels[j].type,
host->secModels[i].labels[j].label);
}
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "</secmodel>\n");
}
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "</host>\n\n");
return 0;
}
static void
virCapabilitiesFormatGuestFeatures(virCapsGuest *guest,
virBuffer *buf)
{
g_auto(virBuffer) childBuf = VIR_BUFFER_INIT_CHILD(buf);
size_t i;
for (i = 0; i < VIR_CAPS_GUEST_FEATURE_TYPE_LAST; i++) {
virCapsGuestFeature *feature = guest->features + i;
if (!feature->present)
continue;
virBufferAsprintf(&childBuf, "<%s", virCapsGuestFeatureInfos[i].name);
if (feature->defaultOn) {
virBufferAsprintf(&childBuf, " default='%s'",
virTristateSwitchTypeToString(feature->defaultOn));
}
if (feature->toggle) {
virBufferAsprintf(&childBuf, " toggle='%s'",
virTristateBoolTypeToString(feature->toggle));
}
virBufferAddLit(&childBuf, "/>\n");
}
virXMLFormatElement(buf, "features", NULL, &childBuf);
}
static void
virCapabilitiesFormatGuestXML(virCapsGuest **guests,
size_t nguests,
virBuffer *buf)
{
size_t i, j, k;
for (i = 0; i < nguests; i++) {
virBufferAddLit(buf, "<guest>\n");
virBufferAdjustIndent(buf, 2);
virBufferAsprintf(buf, "<os_type>%s</os_type>\n",
virDomainOSTypeToString(guests[i]->ostype));
if (guests[i]->arch.id)
virBufferAsprintf(buf, "<arch name='%s'>\n",
virArchToString(guests[i]->arch.id));
virBufferAdjustIndent(buf, 2);
virBufferAsprintf(buf, "<wordsize>%d</wordsize>\n",
guests[i]->arch.wordsize);
if (guests[i]->arch.defaultInfo.emulator)
virBufferAsprintf(buf, "<emulator>%s</emulator>\n",
guests[i]->arch.defaultInfo.emulator);
if (guests[i]->arch.defaultInfo.loader)
virBufferAsprintf(buf, "<loader>%s</loader>\n",
guests[i]->arch.defaultInfo.loader);
for (j = 0; j < guests[i]->arch.defaultInfo.nmachines; j++) {
virCapsGuestMachine *machine = guests[i]->arch.defaultInfo.machines[j];
virBufferAddLit(buf, "<machine");
if (machine->canonical)
virBufferAsprintf(buf, " canonical='%s'", machine->canonical);
if (machine->maxCpus > 0)
virBufferAsprintf(buf, " maxCpus='%d'", machine->maxCpus);
if (machine->deprecated)
virBufferAddLit(buf, " deprecated='yes'");
virBufferAsprintf(buf, ">%s</machine>\n", machine->name);
}
for (j = 0; j < guests[i]->arch.ndomains; j++) {
virBufferAsprintf(buf, "<domain type='%s'",
virDomainVirtTypeToString(guests[i]->arch.domains[j]->type));
if (!guests[i]->arch.domains[j]->info.emulator &&
!guests[i]->arch.domains[j]->info.loader &&
!guests[i]->arch.domains[j]->info.nmachines) {
virBufferAddLit(buf, "/>\n");
continue;
}
virBufferAddLit(buf, ">\n");
virBufferAdjustIndent(buf, 2);
if (guests[i]->arch.domains[j]->info.emulator)
virBufferAsprintf(buf, "<emulator>%s</emulator>\n",
guests[i]->arch.domains[j]->info.emulator);
if (guests[i]->arch.domains[j]->info.loader)
virBufferAsprintf(buf, "<loader>%s</loader>\n",
guests[i]->arch.domains[j]->info.loader);
for (k = 0; k < guests[i]->arch.domains[j]->info.nmachines; k++) {
virCapsGuestMachine *machine = guests[i]->arch.domains[j]->info.machines[k];
virBufferAddLit(buf, "<machine");
if (machine->canonical)
virBufferAsprintf(buf, " canonical='%s'", machine->canonical);
if (machine->maxCpus > 0)
virBufferAsprintf(buf, " maxCpus='%d'", machine->maxCpus);
virBufferAsprintf(buf, ">%s</machine>\n", machine->name);
}
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "</domain>\n");
}
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "</arch>\n");
virCapabilitiesFormatGuestFeatures(guests[i], buf);
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "</guest>\n\n");
}
}
static void
virCapabilitiesFormatStoragePoolXML(virCapsStoragePool **pools,
size_t npools,
virBuffer *buf)
{
size_t i;
if (npools == 0)
return;
virBufferAddLit(buf, "<pool>\n");
virBufferAdjustIndent(buf, 2);
virBufferAddLit(buf, "<enum name='type'>\n");
virBufferAdjustIndent(buf, 2);
for (i = 0; i < npools; i++)
virBufferAsprintf(buf, "<value>%s</value>\n",
virStoragePoolTypeToString(pools[i]->type));
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "</enum>\n");
virBufferAdjustIndent(buf, -2);
virBufferAddLit(buf, "</pool>\n\n");
}
/**
* virCapabilitiesFormatXML:
* @caps: capabilities to format
*
* Convert the capabilities object into an XML representation
*
* Returns the XML document as a string
*/
char *
virCapabilitiesFormatXML(virCaps *caps)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
virBufferAddLit(&buf, "<capabilities>\n\n");
virBufferAdjustIndent(&buf, 2);
if (virCapabilitiesFormatHostXML(&caps->host, &buf) < 0)
return NULL;
virCapabilitiesFormatGuestXML(caps->guests, caps->nguests, &buf);
virCapabilitiesFormatStoragePoolXML(caps->pools, caps->npools, &buf);
virBufferAdjustIndent(&buf, -2);
virBufferAddLit(&buf, "</capabilities>\n");
return virBufferContentAndReset(&buf);
}
/* get the maximum ID of cpus in the host */
static unsigned int
virCapabilitiesHostNUMAGetMaxcpu(virCapsHostNUMA *caps)
{
unsigned int maxcpu = 0;
size_t node;
size_t cpu;
for (node = 0; node < caps->cells->len; node++) {
virCapsHostNUMACell *cell = g_ptr_array_index(caps->cells, node);
for (cpu = 0; cpu < cell->ncpus; cpu++) {
if (cell->cpus[cpu].id > maxcpu)
maxcpu = cell->cpus[cpu].id;
}
}
return maxcpu;
}
/* set cpus of a numa node in the bitmask */
static int
virCapabilitiesHostNUMAGetCellCpus(virCapsHostNUMA *caps,
size_t node,
virBitmap *cpumask)
{
virCapsHostNUMACell *cell = NULL;
size_t cpu;
size_t i;
/* The numa node numbers can be non-contiguous. Ex: 0,1,16,17. */
for (i = 0; i < caps->cells->len; i++) {
cell = g_ptr_array_index(caps->cells, i);
if (cell->num == node)
break;
cell = NULL;
}
for (cpu = 0; cell && cpu < cell->ncpus; cpu++) {
if (virBitmapSetBit(cpumask, cell->cpus[cpu].id) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Cpu '%u' in node '%zu' is out of range "
"of the provided bitmap"),
cell->cpus[cpu].id, node);
return -1;
}
}
return 0;
}
virBitmap *
virCapabilitiesHostNUMAGetCpus(virCapsHostNUMA *caps,
virBitmap *nodemask)
{
g_autoptr(virBitmap) ret = NULL;
unsigned int maxcpu = virCapabilitiesHostNUMAGetMaxcpu(caps);
ssize_t node = -1;
ret = virBitmapNew(maxcpu + 1);
while ((node = virBitmapNextSetBit(nodemask, node)) >= 0) {
if (virCapabilitiesHostNUMAGetCellCpus(caps, node, ret) < 0)
return NULL;
}
return g_steal_pointer(&ret);
}
int
virCapabilitiesHostNUMAGetMaxNode(virCapsHostNUMA *caps)
{
virCapsHostNUMACell *cell = g_ptr_array_index(caps->cells, caps->cells->len - 1);
return cell->num;
}
int
virCapabilitiesGetNodeInfo(virNodeInfoPtr nodeinfo)
{
virArch hostarch = virArchFromHost();
unsigned long long memorybytes;
memset(nodeinfo, 0, sizeof(*nodeinfo));
if (virStrcpyStatic(nodeinfo->model, virArchToString(hostarch)) < 0)
return -1;
if (virHostMemGetInfo(&memorybytes, NULL) < 0)
return -1;
nodeinfo->memory = memorybytes / 1024;
if (virHostCPUGetInfo(hostarch,
&nodeinfo->cpus, &nodeinfo->mhz,
&nodeinfo->nodes, &nodeinfo->sockets,
&nodeinfo->cores, &nodeinfo->threads) < 0)
return -1;
return 0;
}
/* returns 1 on success, 0 if the detection failed and -1 on hard error */
static int
virCapabilitiesFillCPUInfo(int cpu_id G_GNUC_UNUSED,
virCapsHostNUMACellCPU *cpu G_GNUC_UNUSED)
{
#ifdef __linux__
cpu->id = cpu_id;
if (virHostCPUGetSocket(cpu_id, &cpu->socket_id) < 0 ||
virHostCPUGetDie(cpu_id, &cpu->die_id) < 0 ||
virHostCPUGetCore(cpu_id, &cpu->core_id) < 0)
return -1;
if (!(cpu->siblings = virHostCPUGetSiblingsList(cpu_id)))
return -1;
return 0;
#else
virReportError(VIR_ERR_NO_SUPPORT, "%s",
_("node cpu info not implemented on this platform"));
return -1;
#endif
}
static int
virCapabilitiesGetNUMADistances(int node,
virNumaDistance **distancesRet,
int *ndistancesRet)
{
virNumaDistance *tmp = NULL;
int tmp_size = 0;
int ret = -1;
int *distances = NULL;
int ndistances = 0;
size_t i;
if (virNumaGetDistances(node, &distances, &ndistances) < 0)
goto cleanup;
if (!distances) {
*distancesRet = NULL;
*ndistancesRet = 0;
return 0;
}
tmp = g_new0(virNumaDistance, ndistances);
for (i = 0; i < ndistances; i++) {
if (!distances[i])
continue;
tmp[tmp_size].cellid = i;
tmp[tmp_size].value = distances[i];
tmp_size++;
}
VIR_REALLOC_N(tmp, tmp_size);
*ndistancesRet = tmp_size;
*distancesRet = g_steal_pointer(&tmp);
tmp_size = 0;
ret = 0;
cleanup:
VIR_FREE(distances);
VIR_FREE(tmp);
return ret;
}
static int
virCapabilitiesGetNUMAPagesInfo(int node,
virCapsHostNUMACellPageInfo **pageinfo,
int *npageinfo)
{
int ret = -1;
unsigned int *pages_size = NULL;
unsigned long long *pages_avail = NULL;
size_t npages, i;
if (virNumaGetPages(node, &pages_size, &pages_avail, NULL, &npages) < 0)
goto cleanup;
*pageinfo = g_new0(virCapsHostNUMACellPageInfo, npages);
*npageinfo = npages;
for (i = 0; i < npages; i++) {
(*pageinfo)[i].size = pages_size[i];
(*pageinfo)[i].avail = pages_avail[i];
}
ret = 0;
cleanup:
VIR_FREE(pages_avail);
VIR_FREE(pages_size);
return ret;
}
static int
virCapabilitiesGetNodeCacheReadFile(const char *prefix,
const char *dir,
const char *file,
unsigned int *value)
{
g_autofree char *path = g_build_filename(prefix, dir, file, NULL);
int rv = virFileReadValueUint(value, "%s", path);
if (rv < 0) {
if (rv == -2) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("File '%s' does not exist"),
path);
}
return -1;
}
return 0;
}
static int
virCapsHostNUMACellCacheComparator(const void *a,
const void *b)
{
const virNumaCache *aa = a;
const virNumaCache *bb = b;
return aa->level - bb->level;
}
static int
virCapabilitiesGetNodeCache(int node,
GArray **cachesRet)
{
g_autoptr(DIR) dir = NULL;
int direrr = 0;
struct dirent *entry;
g_autofree char *path = NULL;
g_autoptr(GArray) caches = g_array_new(FALSE, FALSE, sizeof(virNumaCache));
path = g_strdup_printf(SYSFS_SYSTEM_PATH "/node/node%d/memory_side_cache", node);
if (virDirOpenIfExists(&dir, path) < 0)
return -1;
while (dir && (direrr = virDirRead(dir, &entry, path)) > 0) {
const char *dname = STRSKIP(entry->d_name, "index");
virNumaCache cache = { 0 };
unsigned int indexing;
unsigned int write_policy;
if (!dname)
continue;
if (virStrToLong_ui(dname, NULL, 10, &cache.level) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unable to parse %s"),
entry->d_name);
return -1;
}
if (virCapabilitiesGetNodeCacheReadFile(path, entry->d_name,
"size", &cache.size) < 0)
return -1;
cache.size >>= 10; /* read in bytes but stored in kibibytes */
if (virCapabilitiesGetNodeCacheReadFile(path, entry->d_name,
"line_size", &cache.line) < 0)
return -1;
if (virCapabilitiesGetNodeCacheReadFile(path, entry->d_name,
"indexing", &indexing) < 0)
return -1;
/* see enum cache_indexing in kernel */
switch (indexing) {
case 0: cache.associativity = VIR_NUMA_CACHE_ASSOCIATIVITY_DIRECT; break;
case 1: cache.associativity = VIR_NUMA_CACHE_ASSOCIATIVITY_FULL; break;
case 2: cache.associativity = VIR_NUMA_CACHE_ASSOCIATIVITY_NONE; break;
default:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unknown indexing value '%u'"),
indexing);
return -1;
}
if (virCapabilitiesGetNodeCacheReadFile(path, entry->d_name,
"write_policy", &write_policy) < 0)
return -1;
/* see enum cache_write_policy in kernel */
switch (write_policy) {
case 0: cache.policy = VIR_NUMA_CACHE_POLICY_WRITEBACK; break;
case 1: cache.policy = VIR_NUMA_CACHE_POLICY_WRITETHROUGH; break;
case 2: cache.policy = VIR_NUMA_CACHE_POLICY_NONE; break;
default:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unknown write_policy value '%u'"),
write_policy);
return -1;
}
g_array_append_val(caches, cache);
}
if (direrr < 0)
return -1;
if (caches->len > 0) {
g_array_sort(caches, virCapsHostNUMACellCacheComparator);
*cachesRet = g_steal_pointer(&caches);
} else {
*cachesRet = NULL;
}
return 0;
}
static int
virCapabilitiesHostNUMAInitFake(virCapsHostNUMA *caps)
{
virNodeInfo nodeinfo;
virCapsHostNUMACellCPU *cpus;
int ncpus;
int n, s, c, t;
int id, cid;
int onlinecpus G_GNUC_UNUSED;
bool tmp;
if (virCapabilitiesGetNodeInfo(&nodeinfo) < 0)
return -1;
ncpus = VIR_NODEINFO_MAXCPUS(nodeinfo);
id = 0;
for (n = 0; n < nodeinfo.nodes; n++) {
int nodecpus = nodeinfo.sockets * nodeinfo.cores * nodeinfo.threads;
cid = 0;
cpus = g_new0(virCapsHostNUMACellCPU, nodecpus);
for (s = 0; s < nodeinfo.sockets; s++) {
for (c = 0; c < nodeinfo.cores; c++) {
g_autoptr(virBitmap) siblings = virBitmapNew(ncpus);
for (t = 0; t < nodeinfo.threads; t++)
ignore_value(virBitmapSetBit(siblings, id + t));
for (t = 0; t < nodeinfo.threads; t++) {
if (virHostCPUGetOnline(id, &tmp) < 0)
goto error;
if (tmp) {
cpus[cid].id = id;
cpus[cid].die_id = 0;
cpus[cid].socket_id = s;
cpus[cid].core_id = c;
cpus[cid].siblings = virBitmapNewCopy(siblings);
cid++;
}
id++;
}
}
}
virCapabilitiesHostNUMAAddCell(caps, 0,
nodeinfo.memory,
cid, &cpus,
0, NULL,
0, NULL,
NULL);
}
return 0;
error:
for (; cid >= 0; cid--)
virBitmapFree(cpus[cid].siblings);
VIR_FREE(cpus);
return -1;
}
static void
virCapabilitiesHostInsertHMAT(GArray *interconnects,
unsigned int initiator,
unsigned int target,
unsigned int read_bandwidth,
unsigned int write_bandwidth,
unsigned int read_latency,
unsigned int write_latency)
{
virNumaInterconnect ni;
ni = (virNumaInterconnect) { VIR_NUMA_INTERCONNECT_TYPE_BANDWIDTH,
initiator, target, 0, VIR_MEMORY_LATENCY_READ, read_bandwidth};
g_array_append_val(interconnects, ni);
ni = (virNumaInterconnect) { VIR_NUMA_INTERCONNECT_TYPE_BANDWIDTH,
initiator, target, 0, VIR_MEMORY_LATENCY_WRITE, write_bandwidth};
g_array_append_val(interconnects, ni);
ni = (virNumaInterconnect) { VIR_NUMA_INTERCONNECT_TYPE_LATENCY,
initiator, target, 0, VIR_MEMORY_LATENCY_READ, read_latency};
g_array_append_val(interconnects, ni);
ni = (virNumaInterconnect) { VIR_NUMA_INTERCONNECT_TYPE_LATENCY,
initiator, target, 0, VIR_MEMORY_LATENCY_WRITE, write_latency};
g_array_append_val(interconnects, ni);
}
static int
virCapabilitiesHostNUMAInitInterconnectsNode(GArray *interconnects,
unsigned int node)
{
g_autofree char *path = NULL;
g_autofree char *initPath = NULL;
g_autoptr(DIR) dir = NULL;
int direrr = 0;
struct dirent *entry;
unsigned int read_bandwidth;
unsigned int write_bandwidth;
unsigned int read_latency;
unsigned int write_latency;
/* Unfortunately, kernel does not expose full HMAT table. I mean it does,
* in its binary form under /sys/firmware/acpi/tables/HMAT but we don't
* want to parse that. But some important info is still exposed, under
* "access0" and "access1" directories. The former contains the best
* interconnect to given node including CPUs and devices that might do I/O
* (such as GPUs and NICs). The latter contains the best interconnect to
* given node but only CPUs are considered. Stick with access1 until sysfs
* exposes the full table in a sensible way.
* NB on most system access0 and access1 contain the same values. */
path = g_strdup_printf(SYSFS_SYSTEM_PATH "/node/node%d/access1", node);
if (!virFileExists(path))
return 0;
if (virCapabilitiesGetNodeCacheReadFile(path, "initiators",
"read_bandwidth",
&read_bandwidth) < 0)
return -1;
if (virCapabilitiesGetNodeCacheReadFile(path, "initiators",
"write_bandwidth",
&write_bandwidth) < 0)
return -1;
/* Bandwidths are read in MiB but stored in KiB */
read_bandwidth <<= 10;
write_bandwidth <<= 10;
if (virCapabilitiesGetNodeCacheReadFile(path, "initiators",
"read_latency",
&read_latency) < 0)
return -1;
if (virCapabilitiesGetNodeCacheReadFile(path, "initiators",
"write_latency",
&write_latency) < 0)
return -1;
initPath = g_strdup_printf("%s/initiators", path);
if (virDirOpen(&dir, initPath) < 0)
return -1;
while ((direrr = virDirRead(dir, &entry, path)) > 0) {
const char *dname = STRSKIP(entry->d_name, "node");
unsigned int initNode;
if (!dname)
continue;
if (virStrToLong_ui(dname, NULL, 10, &initNode) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unable to parse %s"),
entry->d_name);
return -1;
}
virCapabilitiesHostInsertHMAT(interconnects,
initNode, node,
read_bandwidth,
write_bandwidth,
read_latency,
write_latency);
}
return 0;
}
static int
virCapsHostNUMAInterconnectComparator(const void *a,
const void *b)
{
const virNumaInterconnect *aa = a;
const virNumaInterconnect *bb = b;
if (aa->type != bb->type)
return aa->type - bb->type;
if (aa->initiator != bb->initiator)
return aa->initiator - bb->initiator;
if (aa->target != bb->target)
return aa->target - bb->target;
if (aa->cache != bb->cache)
return aa->cache - bb->cache;
if (aa->accessType != bb->accessType)
return aa->accessType - bb->accessType;
return aa->value - bb->value;
}
static int
virCapabilitiesHostNUMAInitInterconnects(virCapsHostNUMA *caps)
{
g_autoptr(DIR) dir = NULL;
int direrr = 0;
struct dirent *entry;
const char *path = SYSFS_SYSTEM_PATH "/node/";
g_autoptr(GArray) interconnects = g_array_new(FALSE, FALSE, sizeof(virNumaInterconnect));
if (virDirOpenIfExists(&dir, path) < 0)
return -1;
while (dir && (direrr = virDirRead(dir, &entry, path)) > 0) {
const char *dname = STRSKIP(entry->d_name, "node");
unsigned int node;
if (!dname)
continue;
if (virStrToLong_ui(dname, NULL, 10, &node) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unable to parse %s"),
entry->d_name);
return -1;
}
if (virCapabilitiesHostNUMAInitInterconnectsNode(interconnects, node) < 0)
return -1;
}
if (interconnects->len > 0) {
g_array_sort(interconnects, virCapsHostNUMAInterconnectComparator);
caps->interconnects = g_steal_pointer(&interconnects);
}
return 0;
}
static int
virCapabilitiesHostNUMAInitReal(virCapsHostNUMA *caps)
{
int n;
virCapsHostNUMACellCPU *cpus = NULL;
int ret = -1;
int ncpus = 0;
int max_node;
if ((max_node = virNumaGetMaxNode()) < 0)
goto cleanup;
for (n = 0; n <= max_node; n++) {
g_autoptr(virBitmap) cpumap = NULL;
g_autofree virNumaDistance *distances = NULL;
int ndistances = 0;
g_autofree virCapsHostNUMACellPageInfo *pageinfo = NULL;
int npageinfo = 0;
unsigned long long memory;
g_autoptr(GArray) caches = NULL;
int cpu;
size_t i;
if ((ncpus = virNumaGetNodeCPUs(n, &cpumap)) < 0) {
if (ncpus == -2)
continue;
ncpus = 0;
goto cleanup;
}
cpus = g_new0(virCapsHostNUMACellCPU, ncpus);
cpu = 0;
for (i = 0; i < virBitmapSize(cpumap); i++) {
if (virBitmapIsBitSet(cpumap, i)) {
if (virCapabilitiesFillCPUInfo(i, cpus + cpu++) < 0)
goto cleanup;
}
}
if (virCapabilitiesGetNUMADistances(n, &distances, &ndistances) < 0)
goto cleanup;
if (virCapabilitiesGetNUMAPagesInfo(n, &pageinfo, &npageinfo) < 0)
goto cleanup;
if (virCapabilitiesGetNodeCache(n, &caches) < 0)
goto cleanup;
/* Detect the amount of memory in the numa cell in KiB */
virNumaGetNodeMemory(n, &memory, NULL);
memory >>= 10;
virCapabilitiesHostNUMAAddCell(caps, n, memory,
ncpus, &cpus,
ndistances, &distances,
npageinfo, &pageinfo,
&caches);
}
if (virCapabilitiesHostNUMAInitInterconnects(caps) < 0)
goto cleanup;
ret = 0;
cleanup:
virCapabilitiesClearHostNUMACellCPUTopology(cpus, ncpus);
VIR_FREE(cpus);
return ret;
}
virCapsHostNUMA *
virCapabilitiesHostNUMANew(void)
{
virCapsHostNUMA *caps = NULL;
caps = g_new0(virCapsHostNUMA, 1);
caps->refs = 1;
caps->cells = g_ptr_array_new_with_free_func(
(GDestroyNotify)virCapabilitiesFreeHostNUMACell);
return caps;
}
virCapsHostNUMA *
virCapabilitiesHostNUMANewHost(void)
{
virCapsHostNUMA *caps = virCapabilitiesHostNUMANew();
if (virNumaIsAvailable()) {
if (virCapabilitiesHostNUMAInitReal(caps) == 0)
return caps;
virCapabilitiesHostNUMAUnref(caps);
caps = virCapabilitiesHostNUMANew();
VIR_WARN("Failed to query host NUMA topology, faking single NUMA node");
}
if (virCapabilitiesHostNUMAInitFake(caps) < 0) {
virCapabilitiesHostNUMAUnref(caps);
return NULL;
}
return caps;
}
int
virCapabilitiesInitPages(virCaps *caps)
{
int ret = -1;
unsigned int *pages_size = NULL;
size_t npages;
if (virNumaGetPages(-1 /* Magic constant for overall info */,
&pages_size, NULL, NULL, &npages) < 0)
goto cleanup;
caps->host.pagesSize = g_steal_pointer(&pages_size);
caps->host.nPagesSize = npages;
npages = 0;
ret = 0;
cleanup:
VIR_FREE(pages_size);
return ret;
}
bool
virCapsHostCacheBankEquals(virCapsHostCacheBank *a,
virCapsHostCacheBank *b)
{
return (a->id == b->id &&
a->level == b->level &&
a->type == b->type &&
a->size == b->size &&
virBitmapEqual(a->cpus, b->cpus));
}
void
virCapsHostCacheBankFree(virCapsHostCacheBank *ptr)
{
size_t i;
if (!ptr)
return;
virBitmapFree(ptr->cpus);
for (i = 0; i < ptr->ncontrols; i++)
g_free(ptr->controls[i]);
g_free(ptr->controls);
g_free(ptr);
}
static int
virCapsHostCacheBankSorter(const void *a,
const void *b)
{
virCapsHostCacheBank *ca = *(virCapsHostCacheBank **)a;
virCapsHostCacheBank *cb = *(virCapsHostCacheBank **)b;
if (ca->level < cb->level)
return -1;
if (ca->level > cb->level)
return 1;
return ca->id - cb->id;
}
static int
virCapabilitiesInitResctrl(virCaps *caps)
{
if (caps->host.resctrl)
return 0;
caps->host.resctrl = virResctrlInfoNew();
if (!caps->host.resctrl)
return -1;
return 0;
}
static int
virCapabilitiesInitResctrlMemory(virCaps *caps)
{
virCapsHostMemBWNode *node = NULL;
size_t i = 0;
int ret = -1;
const virResctrlMonitorType montype = VIR_RESCTRL_MONITOR_TYPE_MEMBW;
const char *prefix = virResctrlMonitorPrefixTypeToString(montype);
for (i = 0; i < caps->host.cache.nbanks; i++) {
virCapsHostCacheBank *bank = caps->host.cache.banks[i];
node = g_new0(virCapsHostMemBWNode, 1);
if (virResctrlInfoGetMemoryBandwidth(caps->host.resctrl,
bank->level, &node->control) > 0) {
node->id = bank->id;
node->cpus = virBitmapNewCopy(bank->cpus);
VIR_APPEND_ELEMENT(caps->host.memBW.nodes, caps->host.memBW.nnodes, node);
}
g_clear_pointer(&node, virCapsHostMemBWNodeFree);
}
if (virResctrlInfoGetMonitorPrefix(caps->host.resctrl, prefix,
&caps->host.memBW.monitor) < 0)
goto cleanup;
ret = 0;
cleanup:
virCapsHostMemBWNodeFree(node);
return ret;
}
int
virCapabilitiesInitCaches(virCaps *caps)
{
size_t i = 0;
g_autoptr(virBitmap) cpus = NULL;
ssize_t pos = -1;
int ret = -1;
struct dirent *ent = NULL;
virCapsHostCacheBank *bank = NULL;
const virResctrlMonitorType montype = VIR_RESCTRL_MONITOR_TYPE_CACHE;
const char *prefix = virResctrlMonitorPrefixTypeToString(montype);
/* Minimum level to expose in capabilities. Can be lowered or removed (with
* the appropriate code below), but should not be increased, because we'd
* lose information. */
const int cache_min_level = 3;
if (virCapabilitiesInitResctrl(caps) < 0)
return -1;
/* offline CPUs don't provide cache info */
if (virFileReadValueBitmap(&cpus, "%s/cpu/online", SYSFS_SYSTEM_PATH) < 0)
return -1;
while ((pos = virBitmapNextSetBit(cpus, pos)) >= 0) {
int rv = -1;
g_autoptr(DIR) dirp = NULL;
g_autofree char *path = g_strdup_printf("%s/cpu/cpu%zd/cache/", SYSFS_SYSTEM_PATH, pos);
rv = virDirOpenIfExists(&dirp, path);
if (rv < 0)
goto cleanup;
if (!dirp)
continue;
while ((rv = virDirRead(dirp, &ent, path)) > 0) {
g_autofree char *type = NULL;
int kernel_type;
unsigned int level;
if (!STRPREFIX(ent->d_name, "index"))
continue;
if (virFileReadValueUint(&level,
"%s/cpu/cpu%zd/cache/%s/level",
SYSFS_SYSTEM_PATH, pos, ent->d_name) < 0)
goto cleanup;
if (level < cache_min_level)
continue;
bank = g_new0(virCapsHostCacheBank, 1);
bank->level = level;
if (virFileReadValueUint(&bank->id,
"%s/cpu/cpu%zd/cache/%s/id",
SYSFS_SYSTEM_PATH, pos, ent->d_name) < 0)
goto cleanup;
if (virFileReadValueUint(&bank->level,
"%s/cpu/cpu%zd/cache/%s/level",
SYSFS_SYSTEM_PATH, pos, ent->d_name) < 0)
goto cleanup;
if (virFileReadValueString(&type,
"%s/cpu/cpu%zd/cache/%s/type",
SYSFS_SYSTEM_PATH, pos, ent->d_name) < 0)
goto cleanup;
if (virFileReadValueScaledInt(&bank->size,
"%s/cpu/cpu%zd/cache/%s/size",
SYSFS_SYSTEM_PATH, pos, ent->d_name) < 0)
goto cleanup;
if (virFileReadValueBitmap(&bank->cpus,
"%s/cpu/cpu%zd/cache/%s/shared_cpu_list",
SYSFS_SYSTEM_PATH, pos, ent->d_name) < 0)
goto cleanup;
kernel_type = virCacheKernelTypeFromString(type);
if (kernel_type < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unknown cache type '%s'"), type);
goto cleanup;
}
bank->type = kernel_type;
for (i = 0; i < caps->host.cache.nbanks; i++) {
if (virCapsHostCacheBankEquals(bank, caps->host.cache.banks[i]))
break;
}
if (i == caps->host.cache.nbanks) {
/* If it is a new cache, then update its resctrl information. */
if (virResctrlInfoGetCache(caps->host.resctrl,
bank->level,
bank->size,
&bank->ncontrols,
&bank->controls) < 0)
goto cleanup;
VIR_APPEND_ELEMENT(caps->host.cache.banks, caps->host.cache.nbanks, bank);
}
g_clear_pointer(&bank, virCapsHostCacheBankFree);
}
if (rv < 0)
goto cleanup;
}
/* Sort the array in order for the tests to be predictable. This way we can
* still traverse the directory instead of guessing names (in case there is
* 'index1' and 'index3' but no 'index2'). */
if (caps->host.cache.banks) {
qsort(caps->host.cache.banks, caps->host.cache.nbanks,
sizeof(*caps->host.cache.banks), virCapsHostCacheBankSorter);
}
if (virCapabilitiesInitResctrlMemory(caps) < 0)
goto cleanup;
if (virResctrlInfoGetMonitorPrefix(caps->host.resctrl, prefix,
&caps->host.cache.monitor) < 0)
goto cleanup;
ret = 0;
cleanup:
virCapsHostCacheBankFree(bank);
return ret;
}
void
virCapabilitiesHostInitIOMMU(virCaps *caps)
{
caps->host.iommu = virHostHasIOMMU();
}