[SeaBIOS] [PATCH v2 2/3] Support for TPM Physical Presence Interface

Stefan Berger stefanb at linux.vnet.ibm.com
Tue May 26 21:48:34 CEST 2015


This patch implements the specification found here:

http://www.trustedcomputinggroup.org/resources/tcg_physical_presence_interface_specification

It adds the necessary BIOS code so that for example an administrator can send
messages from the OS to the BIOS for the BIOS to change the state of the TPM
upon reboot. With the help of this interface, an administrator does not have
to manually interact with the BIOS.

As an example, on Linux the root use can send an opcode to the BIOS through the
TPM's sysfs entries following the opcodes listed in table 2 of the above
specs. To for example disable and deactivate the TPM, the root user would
send opcode '7' to the BIOS:

#> cd /sys/devices/pnp0/00:0?/ppi

#> echo 7 > request

#> reboot

To exchange data between the OS and SeaBIOS, we use the TIS's vendor
specific extensions in locations 0xf90-fff where RAM locations reside
that are not reset during a machine reboot. This memory is initialized
and two locations receive a 32 bit value 'TCG_MAGIC' that the ACPI code
and SeaBIOS are looking for to accept the memory. If after a reboot the
signature is found by SeaBIOS, the opcode (sent from the OS) is looked
at and acted upon.

The implementation requires an ACPI _DSM method to be implemented for the
TPM's SSDT. The code in the _DSM will write the administrator's opcode
into the TIS's RAM locations. The _DSM method is invoked when the root
user interacts with the entries shown in the above ppi sysfs directory.
The patch implementing the _DSM will be posted independently.

This patch supports opcodes 1-11, 14, 21, and 22.

Signed-off-by: Stefan Berger <stefanb at linux.vnet.ibm.com>
---
 src/hw/tpm_drivers.h |   1 +
 src/tcgbios.c        | 577 +++++++++++++++++++++++++++++++++++++++++++++++++++
 src/tcgbios.h        |  44 ++++
 3 files changed, 622 insertions(+)

diff --git a/src/hw/tpm_drivers.h b/src/hw/tpm_drivers.h
index 34bb12d..83e4a62 100644
--- a/src/hw/tpm_drivers.h
+++ b/src/hw/tpm_drivers.h
@@ -53,6 +53,7 @@ extern struct tpm_driver tpm_drivers[];
 #define TIS_REG_DATA_FIFO              0x24
 #define TIS_REG_DID_VID                0xf00
 #define TIS_REG_RID                    0xf04
+#define TIS_REG_RAM                    0xfa0
 
 #define TIS_STS_VALID                  (1 << 7) /* 0x80 */
 #define TIS_STS_COMMAND_READY          (1 << 6) /* 0x40 */
diff --git a/src/tcgbios.c b/src/tcgbios.c
index dc133c5..3263ec6 100644
--- a/src/tcgbios.c
+++ b/src/tcgbios.c
@@ -25,6 +25,7 @@
 #include "sha1.h" // sha1
 #include "fw/paravirt.h" // runningOnXen
 #include "std/smbios.h"
+#include "malloc.h" // malloc_*
 
 static const u8 Startup_ST_CLEAR[2] = { 0x00, TPM_ST_CLEAR };
 static const u8 Startup_ST_STATE[2] = { 0x00, TPM_ST_STATE };
@@ -42,6 +43,11 @@ static const u8 GetCapability_Permanent_Flags[12] = {
     0x00, 0x00, 0x01, 0x08
 };
 
+static const u8 GetCapability_STClear_Flags[12] = {
+    0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04,
+    0x00, 0x00, 0x01, 0x09
+};
+
 static const u8 GetCapability_OwnerAuth[12] = {
     0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04,
     0x00, 0x00, 0x01, 0x11
@@ -68,6 +74,8 @@ static u32 tpm_calling_int19h(void);
 static u32 tpm_add_event_separators(void);
 static u32 tpm_start_option_rom_scan(void);
 static u32 tpm_smbios_measure(void);
+static void tpm_ppi_init(void);
+static void tpm_ppi_process(void);
 
 /* helper functions */
 
@@ -95,6 +103,10 @@ static tpm_state_t tpm_state = {
     .tpm_driver_to_use = TPM_INVALID_DRIVER,
 };
 
+typedef struct {
+    u8  op;
+} tpm_bios_cfg_t;
+
 
 /********************************************************
   Extensions for TCG-enabled BIOS
@@ -443,6 +455,8 @@ tpm_startup(void)
     if (!has_working_tpm())
         return TCG_GENERAL_ERROR;
 
+    tpm_ppi_init();
+
     dprintf(DEBUG_tcg, "TCGBIOS: Starting with TPM_Startup(ST_CLEAR)\n");
     rc = build_and_send_cmd(0, TPM_ORD_Startup,
                             Startup_ST_CLEAR, sizeof(Startup_ST_CLEAR),
@@ -484,6 +498,8 @@ tpm_startup(void)
     if (rc)
         goto err_exit;
 
+    tpm_ppi_process();
+
     rc = tpm_smbios_measure();
     if (rc)
         goto err_exit;
@@ -1489,3 +1505,564 @@ err_exit:
         return rc;
     return TCG_TCG_COMMAND_ERROR;
 }
+
+static u32
+read_stclear_flags(char *buf, int buf_len)
+{
+    u32 rc;
+    u32 returnCode;
+    struct tpm_res_getcap_stclear_flags stcf;
+
+    memset(buf, 0x0, buf_len);
+
+    rc = build_and_send_cmd(0, TPM_ORD_GetCapability,
+                            GetCapability_STClear_Flags,
+                            sizeof(GetCapability_STClear_Flags),
+                            (u8 *)&stcf,
+                            sizeof(struct tpm_res_getcap_stclear_flags),
+                            &returnCode, TPM_DURATION_TYPE_SHORT);
+
+    dprintf(DEBUG_tcg, "TCGBIOS: Return code from TPM_GetCapability() "
+            "= 0x%08x\n", returnCode);
+
+    if (rc || returnCode)
+        goto err_exit;
+
+    memcpy(buf, &stcf.stclear_flags, buf_len);
+
+    return 0;
+
+err_exit:
+    dprintf(DEBUG_tcg, "TCGBIOS: TPM malfunctioning (line %d).\n", __LINE__);
+
+    tpm_state.tpm_working = 0;
+    if (rc)
+        return rc;
+    return TCG_TCG_COMMAND_ERROR;
+}
+
+
+static u32
+assert_physical_presence(int verbose)
+{
+    u32 rc = 0;
+    u32 returnCode;
+    struct tpm_stclear_flags stcf;
+
+    rc = read_stclear_flags((char *)&stcf, sizeof(stcf));
+    if (rc) {
+        dprintf(DEBUG_tcg,
+                "Error reading STClear flags: 0x%08x\n", rc);
+        return rc;
+    }
+
+    if (stcf.flags[STCLEAR_FLAG_IDX_PHYSICAL_PRESENCE])
+        /* physical presence already asserted */
+        return 0;
+
+    rc = build_and_send_cmd(0, TPM_ORD_PhysicalPresence,
+                            PhysicalPresence_CMD_ENABLE,
+                            sizeof(PhysicalPresence_CMD_ENABLE),
+                            NULL, 10, &returnCode, TPM_DURATION_TYPE_SHORT);
+
+    dprintf(DEBUG_tcg,
+           "Return code from TSC_PhysicalPresence(CMD_ENABLE) = 0x%08x\n",
+           returnCode);
+
+    if (rc || returnCode) {
+        if (verbose)
+            printf("Error: Could not enable physical presence.\n\n");
+        goto err_exit;
+    }
+
+    rc = build_and_send_cmd(0, TPM_ORD_PhysicalPresence,
+                            PhysicalPresence_PRESENT,
+                            sizeof(PhysicalPresence_PRESENT),
+                            NULL, 10, &returnCode, TPM_DURATION_TYPE_SHORT);
+
+    dprintf(DEBUG_tcg,
+           "Return code from TSC_PhysicalPresence(PRESENT) = 0x%08x\n",
+           returnCode);
+
+    if (rc || returnCode) {
+        if (verbose)
+            printf("Error: Could not set presence flag.\n\n");
+        goto err_exit;
+    }
+
+    return 0;
+
+err_exit:
+    dprintf(DEBUG_tcg, "TCGBIOS: TPM malfunctioning (line %d).\n", __LINE__);
+
+    tpm_state.tpm_working = 0;
+    if (rc)
+        return rc;
+    return TCG_TCG_COMMAND_ERROR;
+}
+
+
+static u32
+read_permanent_flags(char *buf, int buf_len)
+{
+    u32 rc;
+    u32 returnCode;
+    struct tpm_res_getcap_perm_flags pf;
+
+    memset(buf, 0x0, buf_len);
+
+    rc = build_and_send_cmd(0, TPM_ORD_GetCapability,
+                            GetCapability_Permanent_Flags,
+                            sizeof(GetCapability_Permanent_Flags),
+                            (u8 *)&pf,
+                            sizeof(struct tpm_res_getcap_perm_flags),
+                            &returnCode, TPM_DURATION_TYPE_SHORT);
+
+    dprintf(DEBUG_tcg, "TCGBIOS: Return code from TPM_GetCapability() "
+            "= 0x%08x\n", returnCode);
+
+    if (rc || returnCode)
+        goto err_exit;
+
+    memcpy(buf, &pf.perm_flags, buf_len);
+
+    return 0;
+
+err_exit:
+    dprintf(DEBUG_tcg, "TCGBIOS: TPM malfunctioning (line %d).\n", __LINE__);
+
+    tpm_state.tpm_working = 0;
+    if (rc)
+        return rc;
+    return TCG_TCG_COMMAND_ERROR;
+}
+
+
+static u32
+read_has_owner(u8 *has_owner)
+{
+    u32 rc;
+    u32 returnCode;
+    struct tpm_res_getcap_ownerauth oauth;
+
+    rc = build_and_send_cmd(0, TPM_ORD_GetCapability,
+                            GetCapability_OwnerAuth,
+                            sizeof(GetCapability_OwnerAuth),
+                            (u8 *)&oauth,
+                            sizeof(struct tpm_res_getcap_ownerauth),
+                            &returnCode, TPM_DURATION_TYPE_SHORT);
+
+    dprintf(DEBUG_tcg, "TCGBIOS: Return code from TPM_GetCapability() "
+            "= 0x%08x\n", returnCode);
+
+    if (rc || returnCode)
+        goto err_exit;
+
+    *has_owner = oauth.flag;
+
+    return 0;
+
+err_exit:
+    dprintf(DEBUG_tcg,"TCGBIOS: TPM malfunctioning (line %d).\n", __LINE__);
+
+    tpm_state.tpm_working = 0;
+    if (rc)
+        return rc;
+    return TCG_TCG_COMMAND_ERROR;
+}
+
+
+static u32
+disable_tpm(int disable, u32 *returnCode, int verbose)
+{
+    u32 rc;
+    struct tpm_permanent_flags pf;
+
+    rc = read_permanent_flags((char *)&pf, sizeof(pf));
+    if (rc)
+        return rc;
+
+    if (!!pf.flags[PERM_FLAG_IDX_DISABLE] == !!disable) {
+        if (verbose)
+            printf("TPM is already %s.\n,",
+                   disable ? "disabled" : "enabled");
+        return 0;
+    }
+
+    rc = assert_physical_presence(verbose);
+    if (rc) {
+        dprintf(DEBUG_tcg, "TCGBIOS: Asserting physical presence failed.\n");
+        return rc;
+    }
+
+    rc = build_and_send_cmd(0, disable ? TPM_ORD_PhysicalDisable
+                                       : TPM_ORD_PhysicalEnable,
+                            NULL, 0, NULL, 10, returnCode,
+                            TPM_DURATION_TYPE_SHORT);
+    dprintf(DEBUG_tcg, "Return code from TPM_Physical%sable = 0x%08x\n",
+            disable ? "Dis" : "En", *returnCode);
+
+    if (rc || *returnCode)
+        goto err_exit;
+
+
+    return 0;
+
+err_exit:
+    dprintf(DEBUG_tcg, "TCGBIOS: %sabling the TPM failed.\n",
+            disable ? "Dis" : "En");
+    dprintf(DEBUG_tcg, "TCGBIOS: TPM malfunctioning (line %d).\n", __LINE__);
+
+    tpm_state.tpm_working = 0;
+    if (rc)
+        return rc;
+    return TCG_TCG_COMMAND_ERROR;
+}
+
+
+static u32
+deactivate_tpm(int deactivate, int allow_reset, u32 *returnCode, int verbose)
+{
+    u32 rc;
+    struct tpm_permanent_flags pf;
+
+    rc = read_permanent_flags((char *)&pf, sizeof(pf));
+    if (rc)
+        return rc;
+
+    if (!!pf.flags[PERM_FLAG_IDX_DEACTIVATED] == !!deactivate) {
+        if (verbose)
+            printf("TPM is already %s.\n",
+                   deactivate ? "deactivated" : "activated");
+        return 0;
+    }
+
+    if (pf.flags[PERM_FLAG_IDX_DISABLE]) {
+        if (verbose)
+            printf("TPM must first be enabled.\n");
+        return 0;
+    }
+
+    rc = assert_physical_presence(verbose);
+    if (rc) {
+        dprintf(DEBUG_tcg, "TCGBIOS: Asserting physical presence failed.\n");
+        return rc;
+    }
+
+    rc = build_and_send_cmd(0, TPM_ORD_PhysicalSetDeactivated,
+                            deactivate ? CommandFlag_TRUE
+                                       : CommandFlag_FALSE,
+                            deactivate ? sizeof(CommandFlag_TRUE)
+                                       : sizeof(CommandFlag_FALSE),
+                            NULL, 10, returnCode, TPM_DURATION_TYPE_SHORT);
+
+    dprintf(DEBUG_tcg,
+            "Return code from PhysicalSetDeactivated(%d) = 0x%08x\n",
+            deactivate ? 1 : 0, *returnCode);
+
+    if (rc || *returnCode)
+        goto err_exit;
+
+    if (!deactivate && allow_reset) {
+        if (verbose) {
+            printf("Requiring a reboot to activate the TPM.\n");
+
+            msleep(2000);
+        }
+        extern void reset_vector(void) __noreturn;
+        reset_vector();
+    }
+
+    return 0;
+
+err_exit:
+    dprintf(DEBUG_tcg, "TCGBIOS: TPM malfunctioning (line %d).\n", __LINE__);
+
+    tpm_state.tpm_working = 0;
+    if (rc)
+        return rc;
+    return TCG_TCG_COMMAND_ERROR;
+}
+
+
+static u32
+enable_activate(int allow_reset, u32 *returnCode, int verbose)
+{
+    u32 rc;
+
+    rc = disable_tpm(0, returnCode, verbose);
+    if (rc)
+        return rc;
+
+    rc = deactivate_tpm(0, allow_reset, returnCode, verbose);
+
+    return rc;
+}
+
+
+static u32
+force_clear(int enable_activate_before, int enable_activate_after,
+            u32 *returnCode, int verbose)
+{
+    u32 rc;
+    u8 has_owner;
+
+    rc = read_has_owner(&has_owner);
+    if (rc)
+        return rc;
+    if (!has_owner) {
+        if (verbose)
+            printf("TPM does not have an owner.\n");
+        return 0;
+    }
+
+    if (enable_activate_before) {
+        rc = enable_activate(0, returnCode, verbose);
+        if (rc) {
+            dprintf(DEBUG_tcg,
+                    "TCGBIOS: Enabling/activating the TPM failed.\n");
+            return rc;
+        }
+    }
+
+    rc = assert_physical_presence(verbose);
+    if (rc) {
+        dprintf(DEBUG_tcg, "TCGBIOS: Asserting physical presence failed.\n");
+        return rc;
+    }
+
+    rc = build_and_send_cmd(0, TPM_ORD_ForceClear,
+                            NULL, 0, NULL, 10, returnCode,
+                            TPM_DURATION_TYPE_SHORT);
+
+    dprintf(DEBUG_tcg, "Return code from TPM_ForceClear() = 0x%08x\n",
+            *returnCode);
+
+    if (rc || *returnCode)
+        goto err_exit;
+
+    if (!enable_activate_after) {
+        if (verbose)
+            printf("Owner successfully cleared.\n"
+                   "You will need to enable/activate the TPM again.\n\n");
+        return 0;
+    }
+
+    enable_activate(1, returnCode, verbose);
+
+    return 0;
+
+err_exit:
+    dprintf(DEBUG_tcg, "TCGBIOS: TPM malfunctioning (line %d).\n", __LINE__);
+
+    tpm_state.tpm_working = 0;
+    if (rc)
+        return rc;
+    return TCG_TCG_COMMAND_ERROR;
+}
+
+
+static u32
+set_owner_install(int allow, u32 *returnCode, int verbose)
+{
+    u32 rc;
+    u8 has_owner;
+    struct tpm_permanent_flags pf;
+
+    rc = read_has_owner(&has_owner);
+    if (rc)
+        return rc;
+    if (has_owner) {
+        if (verbose)
+            printf("Must first remove owner.\n");
+        return 0;
+    }
+
+    rc = read_permanent_flags((char *)&pf, sizeof(pf));
+    if (rc)
+        return rc;
+
+    if (pf.flags[PERM_FLAG_IDX_DISABLE]) {
+        if (verbose)
+            printf("TPM must first be enable.\n");
+        return 0;
+    }
+
+    rc = assert_physical_presence(verbose);
+    if (rc) {
+        dprintf(DEBUG_tcg, "TCGBIOS: Asserting physical presence failed.\n");
+        return rc;
+    }
+
+    rc = build_and_send_cmd(0, TPM_ORD_SetOwnerInstall,
+                            (allow) ? CommandFlag_TRUE :
+                                      CommandFlag_FALSE,
+                            sizeof(CommandFlag_TRUE),
+                            NULL, 10, returnCode, TPM_DURATION_TYPE_SHORT);
+
+    dprintf(DEBUG_tcg, "Return code from TPM_SetOwnerInstall() = 0x%08x\n",
+            *returnCode);
+
+    if (rc || *returnCode)
+        goto err_exit;
+
+    if (verbose)
+        printf("Installation of owner %s.\n", allow ? "enabled" : "disabled");
+
+    return 0;
+
+err_exit:
+    dprintf(DEBUG_tcg, "TCGBIOS: TPM malfunctioning (line %d).\n", __LINE__);
+    tpm_state.tpm_working = 0;
+    if (rc)
+        return rc;
+    return TCG_TCG_COMMAND_ERROR;
+}
+
+
+static u32
+tpm_process_cfg(const tpm_bios_cfg_t *cfg, int verbose,
+                u32 *returnCode, u8 *next_step)
+{
+    u32 rc = 0;
+
+    switch (cfg->op) {
+        case 0: /* no-op */
+            break;
+
+        case 1:
+            rc = disable_tpm(0, returnCode, verbose);
+            break;
+
+        case 2:
+            rc = disable_tpm(1, returnCode, verbose);
+            break;
+
+        case 3:
+            rc = deactivate_tpm(0, 1, returnCode, verbose);
+            break;
+
+        case 4:
+            rc = deactivate_tpm(1, 1, returnCode, verbose);
+            break;
+
+        case 5:
+            rc = force_clear(1, 0, returnCode, verbose);
+            break;
+
+        case 6:
+            rc = enable_activate(1, returnCode, verbose);
+            break;
+
+        case 7:
+            rc = deactivate_tpm(1, 1, returnCode, verbose);
+            if (!rc)
+                rc = disable_tpm(1, returnCode, verbose);
+            break;
+
+        case 8:
+            rc = set_owner_install(1, returnCode, verbose);
+            break;
+
+        case 9:
+            rc = set_owner_install(0, returnCode, verbose);
+            break;
+
+        case 10:
+            *next_step = 8;
+            rc = enable_activate(1, returnCode, verbose);
+            /* no reboot happened */
+            if (!rc)
+                rc = set_owner_install(1, returnCode, verbose);
+            break;
+
+        case 11:
+            rc = set_owner_install(0, returnCode, verbose);
+            if (!rc)
+                rc = deactivate_tpm(1, 0, returnCode, verbose);
+            if (!rc)
+                rc = disable_tpm(1, returnCode, verbose);
+            break;
+
+        case 14:
+            rc = force_clear(0, 0, returnCode, verbose);
+            if (!rc)
+                rc = enable_activate(1, returnCode, verbose);
+            break;
+
+        case 21:
+            *next_step = 5;
+            rc = enable_activate(1, returnCode, verbose);
+            /* no reboot happened */
+            if (!rc)
+                rc = force_clear(0, 0, returnCode, verbose);
+            break;
+
+        case 22:
+            *next_step = 14;
+            rc = enable_activate(1, returnCode, verbose);
+            /* no reboot happened */
+            if (!rc)
+                rc = force_clear(0, 0, returnCode, verbose);
+            *next_step = 0;
+            if (!rc)
+                rc = enable_activate(1, returnCode, verbose);
+            break;
+
+        default:
+            break;
+    }
+
+    if (rc)
+        printf("Op %d: An error occurred: 0x%x\n", cfg->op, rc);
+
+    /* no reboot, no next step */
+    *next_step = 0;
+
+    return rc;
+}
+
+static void
+tpm_ppi_init(void)
+{
+    if (runningOnQEMU()) {
+        struct tpm_ppi *tp = (void *)(TPM_TIS_BASE_ADDRESS + TIS_REG_RAM);
+
+        if (tp->sign != TCG_MAGIC) {
+            memset(tp, 0x0, sizeof(*tp));
+            tp->sign = TCG_MAGIC;
+            /* set number of bytes that ACPI can read/write */
+            tp->size = sizeof(tp->opcode) + sizeof(tp->failure) +
+                       sizeof(tp->recent_opcode) + sizeof(tp->response);
+        }
+    }
+}
+
+static void
+tpm_ppi_process(void)
+{
+    tpm_bios_cfg_t cfg;
+
+    if (runningOnQEMU()) {
+        struct tpm_ppi *tp = (void *)(TPM_TIS_BASE_ADDRESS + TIS_REG_RAM);
+
+        if (tp->sign == TCG_MAGIC) {
+            cfg.op = tp->opcode;
+            dprintf(DEBUG_tcg, "TCGBIOS: PPI opcode found: 0x%08x\n", tp->opcode);
+            if (!cfg.op) {
+                /* intermediate step after a reboot? */
+                cfg.op = tp->next_step;
+            } else {
+                /* last full opcode */
+                tp->recent_opcode = cfg.op;
+                tp->opcode = 0;
+            }
+            if (cfg.op) {
+                printf("Processing TPM PPI opcode %d [%d]\n",
+                       tp->recent_opcode, cfg.op);
+                tp->failure = (tpm_process_cfg(&cfg, 0, &tp->response,
+                                               &tp->next_step) != 0);
+            }
+        }
+    }
+}
diff --git a/src/tcgbios.h b/src/tcgbios.h
index a1d0b0f..0fcca15 100644
--- a/src/tcgbios.h
+++ b/src/tcgbios.h
@@ -329,6 +329,36 @@ struct tpm_res_getcap_perm_flags {
 } PACKED;
 
 
+struct tpm_req_getcap_stclear_flags {
+    TPM_REQ_HEADER
+    u32    capArea;
+    u32    subCapSize;
+    u32    subCap;
+} PACKED;
+
+
+struct tpm_stclear_flags {
+    u16    tag;
+    u8     flags[5];
+} PACKED;
+
+
+enum stclearFlagsIndex {
+    STCLEAR_FLAG_IDX_DEACTIVATED = 0,
+    STCLEAR_FLAG_IDX_DISABLE_FORCE_CLEAR,
+    STCLEAR_FLAG_IDX_PHYSICAL_PRESENCE,
+    STCLEAR_FLAG_IDX_PHYSICAL_PRESENCE_LOCK,
+    STCLEAR_FLAG_IDX_GLOBAL_LOCK,
+};
+
+
+struct tpm_res_getcap_stclear_flags {
+    TPM_RSP_HEADER
+    u32    size;
+    struct tpm_stclear_flags stclear_flags;
+} PACKED;
+
+
 struct tpm_res_getcap_ownerauth {
     TPM_RSP_HEADER
     u32    size;
@@ -379,6 +409,20 @@ enum ipltype {
     IPL_EL_TORITO_2
 };
 
+/*
+ * physical presence interface
+ */
+
+struct tpm_ppi {
+    u32 sign;
+    u16 size;           // number of subsequent bytes for ACPI to access
+    u8 opcode;          // set by ACPI
+    u8 failure;         // set by BIOS (0 = success)
+    u8 recent_opcode;   // set by BIOS
+    u32 response;       // set by BIOS
+    u8 next_step;       // BIOS only
+} PACKED;
+
 
 struct bregs;
 void tpm_interrupt_handler32(struct bregs *regs);
-- 
1.9.3




More information about the SeaBIOS mailing list