Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F151970662
D51555.id164578.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
11 KB
Referenced Files
None
Subscribers
None
D51555.id164578.diff
View Options
diff --git a/usr.sbin/bhyve/amd64/bhyverun_machdep.c b/usr.sbin/bhyve/amd64/bhyverun_machdep.c
--- a/usr.sbin/bhyve/amd64/bhyverun_machdep.c
+++ b/usr.sbin/bhyve/amd64/bhyverun_machdep.c
@@ -267,6 +267,366 @@
}
}
+/*
+ * Count the number of "[vcpu.X.]cpuid.*" config options by counting the
+ * number of times being called.
+ */
+static int
+bhyve_count_cpuid_entry(const char *prefix __unused,
+ const nvlist_t *parent __unused, const char *name __unused, int type,
+ void *arg)
+{
+ struct vm_vcpu_cpuid_config *vvcc = arg;
+
+ if (type != NV_TYPE_STRING)
+ errx(4, "bhyve_count_cpuid_entry: "
+ "leaf/index \"%s\" not string type", name);
+
+ vvcc->vvcc_nent++;
+
+ return (0);
+}
+
+/*
+ * Parse a single cpuid options into a struct vcpu_cpuid_entry and place it
+ * into the next slot of the entries array of a struct vm_vcpu_cpuid_config.
+ *
+ * The entries array needs to be pre-allocated large enough to hold all
+ * entries, there is no check for a buffer overflow in this function.
+ */
+static int
+bhyve_add_cpuid_entry(const char *prefix __unused, const nvlist_t *parent,
+ const char *name, int type, void *arg)
+{
+ struct vm_vcpu_cpuid_config *vvcc = arg;
+ struct vcpu_cpuid_entry *vce =
+ &((struct vcpu_cpuid_entry *)vvcc->vvcc_entries)[vvcc->vvcc_nent];
+ u_int regs[4] = { 0 };
+ u_int leaf = 0, index = 0;
+ char *endptr = NULL;
+ const char *value;
+ char *stringp, *val;
+ u_long i;
+
+ if (type != NV_TYPE_STRING)
+ errx(4, "bhyve_add_cpuid_entry: "
+ "leaf/index \"%s\" not string type", name);
+
+ /*
+ * First get the CPUID function and optional index from the name.
+ * The index follows the function, separated by a comma.
+ */
+ stringp = strdup(name);
+ if ((val = strsep(&stringp, ",")) != NULL) {
+ errno = 0;
+ leaf = strtoul(val, &endptr, 0);
+ if (errno != 0 || endptr[0] != '\0')
+ errx(4, "bhyve_add_cpuid_entry: "
+ "invalid CPUID leaf: %s", val);
+ }
+ if ((val = strsep(&stringp, ",")) != NULL) {
+ errno = 0;
+ index = strtoul(val, &endptr, 0);
+ if (errno != 0 || endptr[0] != '\0')
+ errx(4, "bhyve_add_cpuid_entry: "
+ "invalid CPUID index: %s", val);
+ }
+ free(stringp);
+
+ /*
+ * The node value is a list of the values for EAX, EBX, ECX, and EDX,
+ * in that order and separated by commata.
+ */
+ value = nvlist_get_string(parent, name);
+ stringp = strdup(value);
+ i = 0;
+ while ((val = strsep(&stringp, ",")) != NULL) {
+ if (i > nitems(regs))
+ errx(4, "bhyve_add_cpuid_entry: "
+ "too many tokens in CPUID values: %s", value);
+
+ errno = 0;
+ regs[i++] = strtoul(val, &endptr, 0);
+ if (errno != 0 || endptr[0] != '\0')
+ errx(4, "bhyve_add_cpuid_entry: "
+ "invalid CPUID registers value: %s", val);
+ }
+
+ vce->vce_function = leaf;
+ vce->vce_index = index;
+
+ vce->vce_eax = regs[0];
+ vce->vce_ebx = regs[1];
+ vce->vce_ecx = regs[2];
+ vce->vce_edx = regs[3];
+
+ /*
+ * Advance the number of entries, which will also be used when this
+ * function is called again for the next cpuid option to select the
+ * next free slot in the cpuid entries array.
+ */
+ vvcc->vvcc_nent++;
+
+ return (0);
+}
+
+/*
+ * Compare two struct vcpu_cpuid_entry based on CPUID function and index.
+ */
+static int
+bhyve_compare_cpuid_entries(const void *p1, const void *p2)
+{
+ const struct vcpu_cpuid_entry *v1 = p1;
+ const struct vcpu_cpuid_entry *v2 = p2;
+ int cmp;
+
+ /*
+ * Handle the case of duplicates here, which aren't allowed.
+ */
+ if (v1->vce_function == v2->vce_function) {
+ cmp = (v1->vce_index > v2->vce_index) -
+ (v1->vce_index < v2->vce_index);
+ } else {
+ cmp = (v1->vce_function > v2->vce_function) -
+ (v1->vce_function < v2->vce_function);
+ }
+
+ return (cmp);
+}
+
+/*
+ * Populate a struct vm_vcpu_cpuid_config, parsing all cpuid options either for
+ * a particular vcpu or for all vcpus if a NULL vcpu was passed.
+ */
+static void
+bhyve_build_cpuid_config(struct vcpu *vcpu, struct vm_vcpu_cpuid_config *vvcc)
+{
+ char *node_name;
+ nvlist_t *parent;
+ size_t entries_sz;
+
+ bzero(vvcc, sizeof (struct vm_vcpu_cpuid_config));
+
+ if (vcpu != NULL)
+ asprintf(&node_name, "vcpu.%d.cpuid", vcpu_id(vcpu));
+ else
+ asprintf(&node_name, "cpuid");
+
+ if (node_name == NULL)
+ err(4, "Failed to allocate node name for CPUID config");
+
+ parent = find_config_node(node_name);
+
+ /*
+ * If there are no [vcpu.X.]cpuid.* config options, our work here is
+ * done. Mark the cpuid config to use legacy cpuid handling.
+ */
+ if (parent == NULL) {
+ vvcc->vvcc_flags |= VCC_FLAG_LEGACY_HANDLING;
+ return;
+ }
+
+ /*
+ * First, count the options under the parent node so we know how big of
+ * a cpuid entries array we'll need to hold them all.
+ */
+ assert(walk_config_nodes(node_name, parent, vvcc,
+ bhyve_count_cpuid_entry) == 0);
+
+ if (vvcc->vvcc_nent == 0) {
+ /*
+ * This is really unexpected as we should have returned already
+ * above if there were no "[vcpu.X.]cpuid.*" config options.
+ */
+ errx(4, "Failed to parse CPUID config options");
+ }
+
+ entries_sz = sizeof (struct vcpu_cpuid_entry) * vvcc->vvcc_nent;
+ vvcc->vvcc_entries = malloc(entries_sz);
+ if (vvcc->vvcc_entries == NULL)
+ err(4, "Failed to allocate %d CPUID entries", vvcc->vvcc_nent);
+ bzero(vvcc->vvcc_entries, entries_sz);
+
+ /*
+ * Walk all options under the parent again, this time to parse and store
+ * them in the cpuid entries buffer. We clear vvcc_nent so we can use it
+ * to find the next free entry in bhyve_add_cpuid_entry().
+ */
+ vvcc->vvcc_nent = 0;
+ assert(walk_config_nodes(node_name, parent, vvcc,
+ bhyve_add_cpuid_entry) == 0);
+
+ /*
+ * The kernel wants the cpuid entries in sorted in ascending order.
+ */
+ qsort(vvcc->vvcc_entries, vvcc->vvcc_nent,
+ sizeof (struct vcpu_cpuid_entry), bhyve_compare_cpuid_entries);
+}
+
+/*
+ * Merge the cpuid entries of two structs vm_vcpu_cpuid_config into a new
+ * entries array. If an entry for a cpuid function/index found in src already
+ * exists in dst, the one in dst is kept and the one in src is discarded.
+ *
+ * The new merged entries array will replace that of dst.
+ */
+static void
+bhyve_merge_cpuid_config(struct vm_vcpu_cpuid_config *src,
+ struct vm_vcpu_cpuid_config *dst)
+{
+ struct vcpu_cpuid_entry dummy_vce = {
+ .vce_function = UINT32_MAX,
+ .vce_index = UINT32_MAX
+ };
+ struct vcpu_cpuid_entry *src_vce, *src_vce_end;
+ struct vcpu_cpuid_entry *dst_vce, *dst_vce_end;
+ struct vcpu_cpuid_entry *new_entries, *new_vce;
+ size_t entries_sz;
+ uint32_t new_nent, i;
+
+ /* If there are no src cpuid entries, there's nothing to do. */
+ if (src->vvcc_nent == 0)
+ return;
+
+ /*
+ * Allocate a new cpuid entries array big enough to hold all entries of
+ * the src and dst arrays.
+ */
+ new_nent = dst->vvcc_nent + src->vvcc_nent;
+ entries_sz = new_nent * sizeof (struct vcpu_cpuid_entry);
+ new_entries = malloc(entries_sz);
+ memset(new_entries, 0, entries_sz);
+
+ /*
+ * Copy the cpuid entries from src and dst into new. The cpuid entries
+ * in both src and dst are already ordered by function and index, and
+ * we keep it that way. If both function and index are the same, the
+ * dst entry overrides the src one. This of course reduces the total
+ * number of entries in the new merged array.
+ *
+ * The dummy_vce is used when the end of one array is reached, it'll
+ * always compare higher to any "real" cpuid entries so that we'll
+ * continue copying the remaining "real" entries until we're done.
+ */
+ src_vce = src->vvcc_entries;
+ src_vce_end = &src_vce[src->vvcc_nent];
+ dst_vce = dst->vvcc_entries;
+ dst_vce_end = &dst_vce[dst->vvcc_nent];
+
+ for (i = 0; i != new_nent ; i++) {
+ if (src_vce != &dummy_vce && src_vce >= src_vce_end)
+ src_vce = &dummy_vce;
+
+ if (dst_vce != &dummy_vce && dst_vce >= dst_vce_end)
+ dst_vce = &dummy_vce;
+
+ new_vce = &new_entries[i];
+
+
+ if (src_vce->vce_function < dst_vce->vce_function) {
+ *new_vce++ = *src_vce++;
+ } else if (src_vce->vce_function > dst_vce->vce_function) {
+ *new_vce++ = *dst_vce++;
+ } else if (src_vce->vce_index < dst_vce->vce_index) {
+ *new_vce++ = *src_vce++;
+ } else if (src_vce->vce_index > dst_vce->vce_index) {
+ *new_vce++ = *dst_vce++;
+ } else {
+ src_vce++;
+ *new_vce++ = *dst_vce++;
+ new_nent--;
+ }
+ }
+
+ /* If we skipped some entries, reallocate the entries array. */
+ if (new_nent < dst->vvcc_nent + src->vvcc_nent) {
+ entries_sz = new_nent * sizeof (struct vcpu_cpuid_entry);
+ new_entries = realloc(new_entries, entries_sz);
+ if (new_entries == NULL)
+ err(4, "Failed to allocate %d CPUID entries", new_nent);
+ }
+
+ free(dst->vvcc_entries);
+ dst->vvcc_nent = new_nent;
+ dst->vvcc_entries = new_entries;
+ if (new_nent == 0)
+ dst->vvcc_flags |= VCC_FLAG_LEGACY_HANDLING;
+ else
+ dst->vvcc_flags &= ~VCC_FLAG_LEGACY_HANDLING;
+}
+
+/*
+ * Traverse a vcpu config entries array and set the MATCH INDEX flag where
+ * needed. Exit with an error if we encounter any duplicates of function and
+ * index, which aren't allowed.
+ */
+static void
+bhyve_fixup_cpuid_config(struct vm_vcpu_cpuid_config *vvcc)
+{
+ struct vcpu_cpuid_entry *vce, *last_vce;
+
+ last_vce = vvcc->vvcc_entries;
+ if (last_vce == NULL)
+ return;
+
+ for (uint32_t i = 1; i < vvcc->vvcc_nent; i++) {
+ vce = &((struct vcpu_cpuid_entry *)vvcc->vvcc_entries)[i];
+
+ if (vce->vce_function == last_vce->vce_function) {
+ if (vce->vce_index == last_vce->vce_index)
+ errx(4, "duplicate CPUID entry for EAX=%x, "
+ "ECX=%x", vce->vce_function,
+ vce->vce_index);
+ last_vce->vce_flags |= VCE_FLAG_MATCH_INDEX;
+ vce->vce_flags |= VCE_FLAG_MATCH_INDEX;
+ }
+
+ last_vce = vce;
+ }
+}
+
+/*
+ * Build the per-VCPU cpuid configuration from any vcpu.X.cpuid.* config
+ * options, if any. When called for the first time, build the global cpuid
+ * configuration from any cpuid.* config options, if any.
+ */
+static void
+bhyve_init_vcpu_cpuid_config(struct vcpu *vcpu)
+{
+ /* Global VM cpuid config shared by all VCPUs. */
+ static struct vm_vcpu_cpuid_config global_vvcc;
+
+ /*
+ * Per-VCPU cpuid config. We don't currently keep this around as it's
+ * only used by the kernel so far.
+ */
+ struct vm_vcpu_cpuid_config vcpu_vvcc;
+
+ /*
+ * Once the global cpuid config has been built, it'll either have zero
+ * entries and the legacy flag is set, or it'll have non-zero entries
+ * and flags is zero.
+ *
+ * If it hasn't been done yet, do it now.
+ */
+ if (global_vvcc.vvcc_nent == 0 && global_vvcc.vvcc_flags == 0)
+ bhyve_build_cpuid_config(NULL, &global_vvcc);
+
+ /*
+ * Now build the per-VCPU cpuid configuration for this VCPU, which will
+ * merge in the global cpuid configuration.
+ */
+ bhyve_build_cpuid_config(vcpu, &vcpu_vvcc);
+ bhyve_merge_cpuid_config(&global_vvcc, &vcpu_vvcc);
+ bhyve_fixup_cpuid_config(&vcpu_vvcc);
+
+ if (vm_set_cpuid(vcpu, &vcpu_vvcc) != 0)
+ err(4, "vm_set_cpuid()");
+
+ if (vcpu_vvcc.vvcc_entries != NULL)
+ free(vcpu_vvcc.vvcc_entries);
+}
+
void
bhyve_init_vcpu(struct vcpu *vcpu)
{
@@ -303,6 +663,7 @@
exit(4);
}
+ bhyve_init_vcpu_cpuid_config(vcpu);
vm_set_capability(vcpu, VM_CAP_ENABLE_INVPCID, 1);
err = vm_set_capability(vcpu, VM_CAP_IPI_EXIT, 1);
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Apr 12, 8:41 PM (16 h, 6 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
31370286
Default Alt Text
D51555.id164578.diff (11 KB)
Attached To
Mode
D51555: bhyve/amd64: Add support for CPUID configuration
Attached
Detach File
Event Timeline
Log In to Comment