Add a TPM resume quirk for machines where the BIOS doesn't send the TPM_Startup(ST_STATE) to the TPM and subsequent suspends fail due to this. Identify machines by their SMBIOS data and only apply the quirk on those known to need it. Signed-off-by: Stefan Berger --- drivers/char/tpm/tpm.c | 153 +++++++++++++++++++++++++++++++++++++++++++++++++ drivers/char/tpm/tpm.h | 8 ++ 2 files changed, 161 insertions(+) Index: linux-2.6/drivers/char/tpm/tpm.c =================================================================== --- linux-2.6.orig/drivers/char/tpm/tpm.c +++ linux-2.6/drivers/char/tpm/tpm.c @@ -28,6 +28,7 @@ #include #include #include +#include #include "tpm.h" @@ -62,6 +63,131 @@ static LIST_HEAD(tpm_chip_list); static DEFINE_SPINLOCK(driver_lock); static DECLARE_BITMAP(dev_mask, TPM_NUM_DEVICES); +/***** tpm quirks *****/ + +#ifdef CONFIG_DMI + +struct tpm_dmi_entry { + enum dmi_field id; + const char **value; +}; + +struct tpm_resume_quirks { + const char *quirk_name; + const struct tpm_dmi_entry *dmi_entry; +}; + +#define TPM_DMI_ENTRY_LAST \ + { .id = DMI_NONE, .value = (const char*[]) { NULL }, } + +static const struct tpm_resume_quirks tpm_resume_quirks[] = { + { + .quirk_name = "Sony Vaio TX3", + .dmi_entry = (struct tpm_dmi_entry[]) { + { + .id = DMI_BIOS_VENDOR, + .value = (const char*[]) { + "Phoenix Technologies LTD", + NULL, + }, + }, + { + .id = DMI_SYS_VENDOR, + .value = (const char*[]) { + "Sony Corporation", + NULL, + }, + }, + { + .id = DMI_PRODUCT_NAME, + .value = (const char*[]) { + "VGN-TX3XP_B", + NULL, + }, + }, + { + .id = DMI_BOARD_NAME, + .value = (const char*[]) { + "VAIO", + NULL, + }, + }, + TPM_DMI_ENTRY_LAST + } + } +}; + +bool find_str_in_array(const char **array, const char *val) +{ + int i = 0; + + while (array[i]) { + if (!strcmp(array[i++], val)) + return true; + } + + return false; +} + +static bool tpm_resume_quirk_dmi(struct tpm_chip *chip) +{ + int i, j, rc; + bool found = false, handled = false; + const char *val; + const struct tpm_dmi_entry *dmi_entry; + + for (i = 0; i < ARRAY_SIZE(tpm_resume_quirks) && !handled; i++) { + j = 0; + found = true; + + while (true) { + dmi_entry = &tpm_resume_quirks[i].dmi_entry[j]; + + if (dmi_entry->id == DMI_NONE) + break; + + val = dmi_get_system_info(dmi_entry->id); + if (!val) { + found = false; + break; + } + + found = find_str_in_array(dmi_entry->value, val); + if (!found) + break; + j++; + } + + if (found) { + dev_info(chip->dev, + "Using tpm resume quirk '%s'.\n", + tpm_resume_quirks[i].quirk_name); + rc = tpm_startup_ststate(chip); + if (rc < 0) + dev_err(chip->dev, + "quirk: TPM startup(ST_STATE) " + "failed.\n"); + handled = true; + break; + } + } + return handled; +} + +#else + +static bool tpm_resume_quirk_dmi(struct tpm_chip *tpm_chip) +{ + return false; +} + +#endif + +static void tpm_resume_quirk(struct tpm_chip *tpm_chip) +{ + tpm_resume_quirk_dmi(tpm_chip); +} + /* * Array with one entry per ordinal defining the maximum amount * of time the chip could take to return the result. The ordinal @@ -864,6 +990,31 @@ int tpm_do_selftest(struct tpm_chip *chi } EXPORT_SYMBOL_GPL(tpm_do_selftest); +/** + * tpm_startup_ststate - send a ststartup to the TPM + */ +#define TPM_ORD_STARTUP 153 +#define STARTUP_RESULT_SIZE 10 + +static struct tpm_input_header startup_ststate_header = { + .tag = TPM_TAG_RQU_COMMAND, + .length = cpu_to_be32(10), + .ordinal = cpu_to_be32(TPM_ORD_STARTUP), +}; + +int tpm_startup_ststate(struct tpm_chip *chip) +{ + int rc; + struct tpm_cmd_t cmd; + + cmd.header.in = startup_ststate_header; + cmd.params.startup_in.state = cpu_to_be16(TPM_ST_STATE); + rc = transmit_cmd(chip, &cmd, STARTUP_RESULT_SIZE, + "startup(st_state)"); + return rc; +} +EXPORT_SYMBOL_GPL(tpm_startup_ststate); + int tpm_send(u32 chip_num, void *cmd, size_t buflen) { struct tpm_chip *chip; @@ -1313,6 +1464,8 @@ int tpm_pm_resume(struct device *dev) if (chip == NULL) return -ENODEV; + tpm_resume_quirk(chip); + return 0; } EXPORT_SYMBOL_GPL(tpm_pm_resume); Index: linux-2.6/drivers/char/tpm/tpm.h =================================================================== --- linux-2.6.orig/drivers/char/tpm/tpm.h +++ linux-2.6/drivers/char/tpm/tpm.h @@ -42,6 +42,8 @@ enum tpm_addr { #define TPM_ERR_DEACTIVATED 0x6 #define TPM_ERR_DISABLED 0x7 +#define TPM_ST_STATE 0x2 + #define TPM_HEADER_SIZE 10 extern ssize_t tpm_show_pubek(struct device *, struct device_attribute *attr, char *); @@ -269,6 +271,10 @@ struct tpm_pcrextend_in { u8 hash[TPM_DIGEST_SIZE]; }__attribute__((packed)); +struct tpm_startup_in { + __be16 state; +} __packed; + typedef union { struct tpm_getcap_params_out getcap_out; struct tpm_readpubek_params_out readpubek_out; @@ -277,6 +283,7 @@ typedef union { struct tpm_pcrread_in pcrread_in; struct tpm_pcrread_out pcrread_out; struct tpm_pcrextend_in pcrextend_in; + struct tpm_startup_in startup_in; } tpm_cmd_params; struct tpm_cmd_t { @@ -289,6 +296,7 @@ ssize_t tpm_getcap(struct device *, __be extern int tpm_get_timeouts(struct tpm_chip *); extern void tpm_gen_interrupt(struct tpm_chip *); extern int tpm_do_selftest(struct tpm_chip *); +extern int tpm_startup_ststate(struct tpm_chip *); extern unsigned long tpm_calc_ordinal_duration(struct tpm_chip *, u32); extern struct tpm_chip* tpm_register_hardware(struct device *, const struct tpm_vendor_specific *);