| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright 2018 Google LLC |
| * Written by Simon Glass <sjg@chromium.org> |
| */ |
| |
| #define LOG_CATEGORY UCLASS_CROS_NVDATA |
| |
| #include <common.h> |
| #include <dm.h> |
| #include <log.h> |
| #include <tpm_api.h> |
| #include <cros/antirollback.h> |
| #include <cros/nvdata.h> |
| |
| /** |
| * get_index() - Get the index corresponding to a category |
| * |
| * Convert an index into a TPM index |
| * |
| * @index: Index to convert |
| * @return Corresponding TPM index, or -1 if none |
| */ |
| static int get_index(enum cros_nvdata_type type) |
| { |
| switch (type) { |
| case CROS_NV_SECDATAF: |
| return FIRMWARE_NV_INDEX; |
| case CROS_NV_SECDATAK: |
| return KERNEL_NV_INDEX; |
| case CROS_NV_MRC_REC_HASH: |
| return MRC_REC_HASH_NV_INDEX; |
| case CROS_NV_FWMP: |
| return FWMP_NV_INDEX; |
| default: |
| /* We cannot handle these */ |
| break; |
| } |
| log_err("Unsupported type %d\n", type); |
| |
| return -1; |
| } |
| |
| /** |
| * safe_write() - Writes a value safely to the TPM |
| * |
| * This checks for write errors due to hitting the 64-write limit and clears |
| * the TPM when that happens. This can only happen when the TPM is unowned, |
| * so it is OK to clear it (and we really have no choice). This is not expected |
| * to happen frequently, but it could happen. |
| * |
| * @tpm: TPM device |
| * @index: NV index to write to |
| * @data: Data to write |
| * @length: Length of data |
| */ |
| static u32 safe_write(struct udevice *tpm, u32 index, const void *data, |
| u32 length) |
| { |
| u32 ret = tpm_nv_write_value(tpm, index, data, length); |
| |
| if (ret == TPM_MAXNVWRITES) { |
| ret = tpm_clear_and_reenable(tpm); |
| if (ret != TPM_SUCCESS) { |
| log_err("Unable to clear and re-enable TPM\n"); |
| return ret; |
| } |
| ret = tpm_nv_write_value(tpm, index, data, length); |
| } |
| if (ret) { |
| log_err("Failed to write secdata (err=%x)\n", ret); |
| return log_msg_ret("fail", -EIO); |
| } |
| |
| return 0; |
| } |
| |
| int tpm_secdata_read(struct udevice *dev, enum cros_nvdata_type type, u8 *data, |
| int size) |
| { |
| struct udevice *tpm = dev_get_parent(dev); |
| int index; |
| int ret; |
| |
| index = get_index(type); |
| if (index == -1) |
| return -EINVAL; |
| |
| ret = tpm_nv_read_value(tpm, index, data, size); |
| if (ret == TPM_BADINDEX) { |
| return log_msg_ret("type", -ENOENT); |
| } else if (ret == TPM2_RC_COMMAND_CODE) { |
| return log_msg_ret("cmd", -ENOSYS); |
| } else if (ret != TPM_SUCCESS) { |
| log_err("Failed to read secdata (err=%x)\n", ret); |
| return log_msg_ret("fail", -EIO); |
| } |
| |
| return 0; |
| } |
| |
| static int tpm_secdata_write(struct udevice *dev, enum cros_nvdata_type type, |
| const u8 *data, int size) |
| { |
| struct udevice *tpm = dev_get_parent(dev); |
| int index; |
| int ret; |
| |
| index = get_index(type); |
| if (index == -1) |
| return -EINVAL; |
| |
| ret = safe_write(tpm, index, data, size); |
| if (ret != TPM_SUCCESS) { |
| log_err("Failed to write secdata (err=%x)\n", ret); |
| return log_msg_ret("fail", -EIO); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Similarly to safe_write(), this ensures we don't fail a DefineSpace because |
| * we hit the TPM write limit. This is even less likely to happen than with |
| * writes because we only define spaces once at initialisation, but we'd |
| * rather be paranoid about this. |
| */ |
| static u32 safe_define_space(struct udevice *tpm, u32 index, u32 perm, u32 size) |
| { |
| u32 result; |
| |
| result = tpm1_nv_define_space(tpm, index, perm, size); |
| if (result == TPM_MAXNVWRITES) { |
| result = tpm_clear_and_reenable(tpm); |
| if (result != TPM_SUCCESS) |
| return result; |
| return tpm1_nv_define_space(tpm, index, perm, size); |
| } else { |
| return result; |
| } |
| } |
| |
| static uint32_t set_space(struct udevice *tpm, uint index, uint attr, uint size, |
| const u8 *nv_policy, size_t nv_policy_size) |
| { |
| uint32_t rv; |
| |
| rv = tpm2_nv_define_space(tpm, index, size, attr, nv_policy, |
| nv_policy_size); |
| if (rv == TPM2_RC_NV_DEFINED) { |
| /* |
| * Continue with writing: it may be defined, but not written |
| * to. In that case a subsequent tlcl_read() would still return |
| * TPM_E_BADINDEX on TPM 2.0. The cases when some non-firmware |
| * space is defined while the firmware space is not there |
| * should be rare (interrupted initialization), so no big harm |
| * in writing once again even if it was written already. |
| */ |
| log_debug("%#x space already exists\n", index); |
| rv = TPM_SUCCESS; |
| } |
| |
| if (rv != TPM_SUCCESS) |
| return rv; |
| |
| return 0; |
| } |
| |
| static int tpm_secdata_setup(struct udevice *dev, enum cros_nvdata_type type, |
| uint attr, uint size, const u8 *nv_policy, |
| int nv_policy_size) |
| { |
| struct udevice *tpm = dev_get_parent(dev); |
| int ret; |
| int index; |
| |
| index = get_index(type); |
| if (index == -1) |
| return -EINVAL; |
| log_info("index=%x\n", index); |
| |
| if (tpm_is_v1(tpm)) |
| ret = safe_define_space(tpm, index, attr, size); |
| else if (tpm_is_v2(tpm)) |
| ret = set_space(tpm, index, attr, size, nv_policy, |
| nv_policy_size); |
| else |
| return log_msg_ret("version", -ENOENT); |
| if (ret != TPM_SUCCESS) { |
| log_err("Failed to setup secdata (err=%x)\n", ret); |
| return log_msg_ret("fail", -EIO); |
| } |
| |
| return 0; |
| } |
| |
| static int tpm_secdata_lock(struct udevice *dev, enum cros_nvdata_type type) |
| { |
| struct udevice *tpm = dev_get_parent(dev); |
| enum tpm_version version = tpm_get_version(tpm); |
| int index; |
| |
| index = get_index(type); |
| if (index == -1) |
| return -EINVAL; |
| |
| if (IS_ENABLED(CONFIG_TPM_V1) && version == TPM_V1) { |
| /* |
| * We only have a global lock. Lock it when the firmware space |
| * is requested, and do nothing otherwise. This ensures that the |
| * lock is always set. |
| */ |
| if (type == CROS_NV_SECDATAF) |
| return tpm_set_global_lock(tpm); |
| } else if (IS_ENABLED(CONFIG_TPM_V2) && version == TPM_V2) { |
| if (type == CROS_NV_SECDATAF || type == CROS_NV_MRC_REC_HASH) |
| return log_retz(tpm2_write_lock(tpm, index)); |
| else if (type == CROS_NV_SECDATAK) |
| return log_retz(tpm2_disable_platform_hierarchy(tpm)); |
| else |
| return log_msg_ret("type", -ENOTSUPP); |
| } else { |
| return log_msg_ret("no tpm", -ENOENT); |
| } |
| |
| return 0; |
| } |
| |
| static const struct cros_nvdata_ops tpm_secdata_ops = { |
| .read = tpm_secdata_read, |
| .write = tpm_secdata_write, |
| .setup = tpm_secdata_setup, |
| .lock = tpm_secdata_lock, |
| }; |
| |
| static const struct udevice_id tpm_secdata_ids[] = { |
| { .compatible = "google,tpm-secdata" }, |
| { } |
| }; |
| |
| U_BOOT_DRIVER(google_tpm_secdata) = { |
| .name = "google_tpm_secdata", |
| .id = UCLASS_CROS_NVDATA, |
| .of_match = tpm_secdata_ids, |
| .ops = &tpm_secdata_ops, |
| .of_to_plat = cros_nvdata_of_to_plat, |
| }; |