summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Brown2013-08-06 20:16:30 +0200
committerMichael Brown2013-08-07 13:06:28 +0200
commit55201e2d0e60003edfd7e2c7c4c592136b000f44 (patch)
tree3be4677eaac90b8c686edf1318a8726895868649
parent[nfs] Fix an issue with the selection of a local port (diff)
downloadipxe-55201e2d0e60003edfd7e2c7c4c592136b000f44.tar.gz
ipxe-55201e2d0e60003edfd7e2c7c4c592136b000f44.tar.xz
ipxe-55201e2d0e60003edfd7e2c7c4c592136b000f44.zip
[settings] Expose CPUID instruction via settings mechanism
Allow CPUID values to be read using the syntax ${cpuid/<register>.<function>} For example, ${cpuid/2.0x80000001} will give the value of %ecx after calling CPUID with %eax=0x80000001. Values for <register> are encoded as %eax=0, %ebx=1, %ecx=2, %edx=3. The numeric encoding is more sophisticated than described above, allowing for settings such as the CPU model (obtained by calling CPUID with %eax=0x80000002-0x80000004 inclusive and concatenating the values returned in %eax:%ebx:%ecx:%edx). See the source code for details. The "cpuvendor" and "cpumodel" settings provide easy access to these more complex CPUID settings. This functionality is intended to complement the "cpuid" command, which allows for testing individual CPUID feature bits. Signed-off-by: Michael Brown <mcb30@ipxe.org>
-rw-r--r--src/arch/x86/core/cpuid.c20
-rw-r--r--src/arch/x86/core/cpuid_settings.c272
-rw-r--r--src/arch/x86/include/bits/errfile.h1
-rw-r--r--src/arch/x86/include/ipxe/cpuid.h25
-rw-r--r--src/config/config.c3
-rw-r--r--src/config/settings.h1
6 files changed, 303 insertions, 19 deletions
diff --git a/src/arch/x86/core/cpuid.c b/src/arch/x86/core/cpuid.c
index 96f79409..5908f441 100644
--- a/src/arch/x86/core/cpuid.c
+++ b/src/arch/x86/core/cpuid.c
@@ -33,7 +33,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
*
* @ret is_supported CPUID instruction is supported
*/
-static int cpuid_is_supported ( void ) {
+int cpuid_is_supported ( void ) {
unsigned long original;
unsigned long inverted;
@@ -53,24 +53,6 @@ static int cpuid_is_supported ( void ) {
}
/**
- * Issue CPUID instruction
- *
- * @v operation CPUID operation
- * @v eax Output via %eax
- * @v ebx Output via %ebx
- * @v ecx Output via %ecx
- * @v edx Output via %edx
- */
-static inline __attribute__ (( always_inline )) void
-cpuid ( uint32_t operation, uint32_t *eax, uint32_t *ebx, uint32_t *ecx,
- uint32_t *edx ) {
-
- __asm__ ( "cpuid"
- : "=a" ( *eax ), "=b" ( *ebx ), "=c" ( *ecx ), "=d" ( *edx )
- : "0" ( operation ) );
-}
-
-/**
* Get Intel-defined x86 CPU features
*
* @v features x86 CPU features to fill in
diff --git a/src/arch/x86/core/cpuid_settings.c b/src/arch/x86/core/cpuid_settings.c
new file mode 100644
index 00000000..e2ec9bde
--- /dev/null
+++ b/src/arch/x86/core/cpuid_settings.c
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2013 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <string.h>
+#include <errno.h>
+#include <byteswap.h>
+#include <ipxe/init.h>
+#include <ipxe/settings.h>
+#include <ipxe/cpuid.h>
+
+/** @file
+ *
+ * x86 CPUID settings
+ *
+ * CPUID settings are numerically encoded as:
+ *
+ * Bit 31 Extended function
+ * Bits 30-28 Unused
+ * Bits 27-24 Number of consecutive functions to call, minus one
+ * Bit 23 Return result as little-endian (used for strings)
+ * Bits 22-18 Unused
+ * Bits 17-16 Number of registers in register array, minus one
+ * Bits 15-8 Array of register indices. First entry in array is in
+ * bits 9-8. Indices are 0-%eax, 1-%ebx, 2-%ecx, 3-%edx.
+ * Bits 7-0 Starting function number (excluding "extended" bit)
+ *
+ * This encoding scheme is designed to allow the common case of
+ * extracting a single register from a single function to be encoded
+ * using "cpuid/<register>.<function>", e.g. "cpuid/2.0x80000001" to
+ * retrieve the value of %ecx from calling CPUID with %eax=0x80000001.
+ */
+
+/** CPUID setting tag register indices */
+enum cpuid_registers {
+ CPUID_EAX = 0,
+ CPUID_EBX = 1,
+ CPUID_ECX = 2,
+ CPUID_EDX = 3,
+};
+
+/**
+ * Construct CPUID setting tag
+ *
+ * @v function Starting function number
+ * @v num_functions Number of consecutive functions
+ * @v little_endian Return result as little-endian
+ * @v num_registers Number of registers in register array
+ * @v register1 First register in register array (or zero, if empty)
+ * @v register2 Second register in register array (or zero, if empty)
+ * @v register3 Third register in register array (or zero, if empty)
+ * @v register4 Fourth register in register array (or zero, if empty)
+ * @ret tag Setting tag
+ */
+#define CPUID_TAG( function, num_functions, little_endian, num_registers, \
+ register1, register2, register3, register4 ) \
+ ( (function) | ( ( (num_functions) - 1 ) << 24 ) | \
+ ( (little_endian) << 23 ) | ( ( (num_registers) - 1) << 16 ) | \
+ ( (register1) << 8 ) | ( (register2) << 10 ) | \
+ ( (register3) << 12 ) | ( (register4) << 14 ) )
+
+/**
+ * Extract endianness from CPUID setting tag
+ *
+ * @v tag Setting tag
+ * @ret little_endian Result should be returned as little-endian
+ */
+#define CPUID_LITTLE_ENDIAN( tag ) ( (tag) & 0x00800000UL )
+
+/**
+ * Extract starting function number from CPUID setting tag
+ *
+ * @v tag Setting tag
+ * @ret function Starting function number
+ */
+#define CPUID_FUNCTION( tag ) ( (tag) & 0x800000ffUL )
+
+/**
+ * Extract number of consecutive functions from CPUID setting tag
+ *
+ * @v tag Setting tag
+ * @ret num_functions Number of consecutive functions
+ */
+#define CPUID_NUM_FUNCTIONS( tag ) ( ( ( (tag) >> 24 ) & 0xf ) + 1 )
+
+/**
+ * Extract register array from CPUID setting tag
+ *
+ * @v tag Setting tag
+ * @ret registers Register array
+ */
+#define CPUID_REGISTERS( tag ) ( ( (tag) >> 8 ) & 0xff )
+
+/**
+ * Extract number of registers from CPUID setting tag
+ *
+ * @v tag Setting tag
+ * @ret num_registers Number of registers within register array
+ */
+#define CPUID_NUM_REGISTERS( tag ) ( ( ( (tag) >> 16 ) & 0x3 ) + 1 )
+
+/** CPUID settings scope */
+static struct settings_scope cpuid_settings_scope;
+
+/**
+ * Check applicability of CPUID setting
+ *
+ * @v settings Settings block
+ * @v setting Setting
+ * @ret applies Setting applies within this settings block
+ */
+static int cpuid_settings_applies ( struct settings *settings __unused,
+ struct setting *setting ) {
+
+ return ( setting->scope == &cpuid_settings_scope );
+}
+
+/**
+ * Fetch value of CPUID setting
+ *
+ * @v settings Settings block
+ * @v setting Setting to fetch
+ * @v data Buffer to fill with setting data
+ * @v len Length of buffer
+ * @ret len Length of setting data, or negative error
+ */
+static int cpuid_settings_fetch ( struct settings *settings,
+ struct setting *setting,
+ void *data, size_t len ) {
+ uint32_t function;
+ uint32_t max_function;
+ uint32_t num_functions;
+ uint32_t registers;
+ uint32_t num_registers;
+ uint32_t buf[4];
+ uint32_t output;
+ uint32_t discard_b;
+ uint32_t discard_c;
+ uint32_t discard_d;
+ size_t frag_len;
+ size_t result_len = 0;
+
+ /* Fail unless CPUID is supported */
+ if ( ! cpuid_is_supported() ) {
+ DBGC ( settings, "CPUID not supported\n" );
+ return -ENOTSUP;
+ }
+
+ /* Find highest supported function number within this set */
+ function = CPUID_FUNCTION ( setting->tag );
+ cpuid ( function & CPUID_EXTENDED, &max_function, &discard_b,
+ &discard_c, &discard_d );
+
+ /* Fail if maximum function number is meaningless (e.g. if we
+ * are attempting to call an extended function on a CPU which
+ * does not support them).
+ */
+ if ( ( max_function & CPUID_AMD_CHECK_MASK ) !=
+ ( function & CPUID_AMD_CHECK_MASK ) ) {
+ DBGC ( settings, "CPUID invalid maximum function\n" );
+ return -ENOTSUP;
+ }
+
+ /* Call each function in turn */
+ num_functions = CPUID_NUM_FUNCTIONS ( setting->tag );
+ for ( ; num_functions-- ; function++ ) {
+
+ /* Fail if this function is not supported */
+ if ( function > max_function ) {
+ DBGC ( settings, "CPUID function %#08x not supported\n",
+ function );
+ return -ENOTSUP;
+ }
+
+ /* Issue CPUID */
+ cpuid ( function, &buf[CPUID_EAX], &buf[CPUID_EBX],
+ &buf[CPUID_ECX], &buf[CPUID_EDX] );
+ DBGC ( settings, "CPUID %#08x => %#08x:%#08x:%#08x:%#08x\n",
+ function, buf[0], buf[1], buf[2], buf[3] );
+
+ /* Copy results to buffer */
+ registers = CPUID_REGISTERS ( setting->tag );
+ num_registers = CPUID_NUM_REGISTERS ( setting->tag );
+ for ( ; num_registers-- ; registers >>= 2 ) {
+ output = buf[ registers & 0x3 ];
+ if ( ! CPUID_LITTLE_ENDIAN ( setting->tag ) )
+ output = cpu_to_be32 ( output );
+ frag_len = sizeof ( output );
+ if ( frag_len > len )
+ frag_len = len;
+ memcpy ( data, &output, frag_len );
+ data += frag_len;
+ len -= frag_len;
+ result_len += sizeof ( output );
+ }
+ }
+
+ /* Set type if not already specified */
+ if ( ! setting->type )
+ setting->type = &setting_type_hexraw;
+
+ return result_len;
+}
+
+/** CPUID settings operations */
+static struct settings_operations cpuid_settings_operations = {
+ .applies = cpuid_settings_applies,
+ .fetch = cpuid_settings_fetch,
+};
+
+/** CPUID settings */
+static struct settings cpuid_settings = {
+ .refcnt = NULL,
+ .siblings = LIST_HEAD_INIT ( cpuid_settings.siblings ),
+ .children = LIST_HEAD_INIT ( cpuid_settings.children ),
+ .op = &cpuid_settings_operations,
+ .default_scope = &cpuid_settings_scope,
+};
+
+/** Initialise CPUID settings */
+static void cpuid_settings_init ( void ) {
+ int rc;
+
+ if ( ( rc = register_settings ( &cpuid_settings, NULL,
+ "cpuid" ) ) != 0 ) {
+ DBG ( "CPUID could not register settings: %s\n",
+ strerror ( rc ) );
+ return;
+ }
+}
+
+/** CPUID settings initialiser */
+struct init_fn cpuid_settings_init_fn __init_fn ( INIT_NORMAL ) = {
+ .initialise = cpuid_settings_init,
+};
+
+/** CPUID predefined settings */
+struct setting cpuid_predefined_settings[] __setting ( SETTING_HOST_EXTRA ) = {
+ {
+ .name = "cpuvendor",
+ .description = "CPU vendor",
+ .tag = CPUID_TAG ( CPUID_VENDOR_ID, 1, 1, 3,
+ CPUID_EBX, CPUID_EDX, CPUID_ECX, 0 ),
+ .type = &setting_type_string,
+ .scope = &cpuid_settings_scope,
+ },
+ {
+ .name = "cpumodel",
+ .description = "CPU model",
+ .tag = CPUID_TAG ( CPUID_MODEL, 3, 1, 4,
+ CPUID_EAX, CPUID_EBX, CPUID_ECX, CPUID_EDX ),
+ .type = &setting_type_string,
+ .scope = &cpuid_settings_scope,
+ },
+};
diff --git a/src/arch/x86/include/bits/errfile.h b/src/arch/x86/include/bits/errfile.h
index bfd22385..d9cf5c45 100644
--- a/src/arch/x86/include/bits/errfile.h
+++ b/src/arch/x86/include/bits/errfile.h
@@ -46,6 +46,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
#define ERRFILE_timer_bios ( ERRFILE_ARCH | ERRFILE_DRIVER | 0x00010000 )
#define ERRFILE_cpuid_cmd ( ERRFILE_ARCH | ERRFILE_OTHER | 0x00000000 )
+#define ERRFILE_cpuid_settings ( ERRFILE_ARCH | ERRFILE_OTHER | 0x00010000 )
/** @} */
diff --git a/src/arch/x86/include/ipxe/cpuid.h b/src/arch/x86/include/ipxe/cpuid.h
index 9705137c..2f78dfca 100644
--- a/src/arch/x86/include/ipxe/cpuid.h
+++ b/src/arch/x86/include/ipxe/cpuid.h
@@ -30,6 +30,9 @@ struct x86_features {
/** CPUID support flag */
#define CPUID_FLAG 0x00200000UL
+/** CPUID extended function */
+#define CPUID_EXTENDED 0x80000000UL
+
/** Get vendor ID and largest standard function */
#define CPUID_VENDOR_ID 0x00000000UL
@@ -48,6 +51,28 @@ struct x86_features {
/** Get extended features */
#define CPUID_AMD_FEATURES 0x80000001UL
+/** Get CPU model */
+#define CPUID_MODEL 0x80000002UL
+
+/**
+ * Issue CPUID instruction
+ *
+ * @v operation CPUID operation
+ * @v eax Output via %eax
+ * @v ebx Output via %ebx
+ * @v ecx Output via %ecx
+ * @v edx Output via %edx
+ */
+static inline __attribute__ (( always_inline )) void
+cpuid ( uint32_t operation, uint32_t *eax, uint32_t *ebx, uint32_t *ecx,
+ uint32_t *edx ) {
+
+ __asm__ ( "cpuid"
+ : "=a" ( *eax ), "=b" ( *ebx ), "=c" ( *ecx ), "=d" ( *edx )
+ : "0" ( operation ) );
+}
+
+extern int cpuid_is_supported ( void );
extern void x86_features ( struct x86_features *features );
#endif /* _IPXE_CPUID_H */
diff --git a/src/config/config.c b/src/config/config.c
index 437f6133..06c5713f 100644
--- a/src/config/config.c
+++ b/src/config/config.c
@@ -313,6 +313,9 @@ REQUIRE_OBJECT ( pci_settings );
#ifdef VMWARE_SETTINGS
REQUIRE_OBJECT ( guestinfo );
#endif
+#ifdef CPUID_SETTINGS
+REQUIRE_OBJECT ( cpuid_settings );
+#endif
/*
* Drag in selected keyboard map
diff --git a/src/config/settings.h b/src/config/settings.h
index 9c5c2d20..97131e88 100644
--- a/src/config/settings.h
+++ b/src/config/settings.h
@@ -10,6 +10,7 @@
FILE_LICENCE ( GPL2_OR_LATER );
#define PCI_SETTINGS /* PCI device settings */
+//#define CPUID_SETTINGS /* CPUID settings */
//#define VMWARE_SETTINGS /* VMware GuestInfo settings */
#include <config/local/settings.h>