Skip to content

Commit

Permalink
[settings] Expose CPUID instruction via settings mechanism
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
mcb30 committed Aug 7, 2013
1 parent 53c01d6 commit 55201e2
Show file tree
Hide file tree
Showing 6 changed files with 303 additions and 19 deletions.
20 changes: 1 addition & 19 deletions src/arch/x86/core/cpuid.c
Expand Up @@ -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;

Expand All @@ -52,24 +52,6 @@ static int cpuid_is_supported ( void ) {
return ( ( original ^ inverted ) & CPUID_FLAG );
}

/**
* 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
*
Expand Down
272 changes: 272 additions & 0 deletions 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,
},
};
1 change: 1 addition & 0 deletions src/arch/x86/include/bits/errfile.h
Expand Up @@ -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 )

/** @} */

Expand Down
25 changes: 25 additions & 0 deletions src/arch/x86/include/ipxe/cpuid.h
Expand Up @@ -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

Expand All @@ -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 */
3 changes: 3 additions & 0 deletions src/config/config.c
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/config/settings.h
Expand Up @@ -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>
Expand Down

0 comments on commit 55201e2

Please sign in to comment.