// SPDX-License-Identifier: GPL-2+ #define pr_fmt(fmt) "avm-prom: %s: " fmt, __func__ #include #include #include #include #include #include #include #include #include #define PLATFORM_MTD "Platform Data" #define FDR_MAGIC 0x0FF0A55Au #define FDR_MAGIC_OFF 0x10u #define FDR_SECTIONS_OFF 0x40u #define FDR_SECTIONS_NUM 5u #define FDR_SECTION_DESC 0u #define FDR_SECTION_BIOS 1u #define FDR_SECTION_ME 2u #define FDR_SECTION_GBE 3u #define FDR_SECTION_PDR 4u // PDR Image Directory Entry in the first page of PDR struct pdr_image_dir { __le32 id; __le32 offset; __le32 size; __le32 reserved; } __packed; #define SECTOR_SIZE 0x1000u #define PDR_IMAGE_DIR_MAX_CNT (SECTOR_SIZE / sizeof(struct pdr_image_dir)) #define PDR_IMAGE_ID_IMG_DIR 0x1d000000u #define PDR_IMAGE_ID_AID0 0x0000A1D0u #define PDR_IMAGE_ID_AID1 0x0000A1D1u #define PDR_IMAGE_ID_OEMD0 0x00000ED0u #define PDR_IMAGE_ID_END 0xFFFFFFFFu enum env_var_type { env_once, env_overwrite }; struct __packed env_var { __be32 type; __be32 data_len; unsigned char data[]; }; static void restore_finalization(enum avm_prom_config_type typ) { struct avm_efivar_resource *res; struct env_var *entry; const char *name, *value_end; char *efi_value, *value; unsigned int data_len; ssize_t offset, len; int result; void *data; bool overwrite; if (typ != FINAL && typ != PRODENV) { pr_emerg("Invalid configuration data type %d\n", typ); BUG(); } len = avm_prom_get_config_alloc(typ, &data); if (len < 0) { pr_err("Error reading avm-prom config entry %d: %d\n", typ, len); return; } if (len == 0) goto out; res = avm_efivar_resource_create(); if (IS_ERR(res)) { pr_err("Out of memory\n"); goto out; } offset = 0; while (offset < len) { result = -EILSEQ; entry = data + offset; data_len = be32_to_cpu(entry->data_len); if (offset + data_len > len) break; name = (const char *)(entry + 1); value = memchr(name, '\0', data_len); if (!value) break; ++value; value_end = memchr(value, '\0', data_len - (value - name)); if (!value_end) break; if (++value_end - name != data_len) break; efi_value = avm_efivar_get(res, name, NULL); result = PTR_ERR_OR_ZERO(efi_value); if (result && result != -ENOENT) { pr_err("Error reading EFI variable %s: %d\n", name, result); break; } /* * Production/default environment never overrides * present variables */ if (typ == PRODENV) overwrite = false; else overwrite = be32_to_cpu(entry->type) == env_overwrite; if (result == -ENOENT || (overwrite && strcmp(value, efi_value))) { pr_info("Restoring %s -> %s (%s)\n", name, value, overwrite ? "overwrite" : "once"); result = avm_efivar_set(res, NULL, value, strlen(value)+1); if (result != 0) pr_err("Error restoring value for %s: %d\n", name, result); } offset += sizeof(*entry) + data_len; } if (result == -EILSEQ) pr_err("Environment data corrupt!\n"); avm_efivar_resource_destroy(res); out: kvfree(data); } static struct avm_prom_mtd_device avm_prom_mtd; static bool avm_prom_server_ready; #define AVM_PART_TFFS1_UUID "58f796b7-2cd3-5a0a-ba8c-e009dc6db3a7" static int mtd_add_tffs(struct mtd_info *mtd) { struct hd_struct *part; struct gendisk *disk; int partno; int result = -ENODEV; if (!mtd->dev.parent) return -ENODEV; disk = get_gendisk(mtd->dev.parent->devt, &partno); if (!disk) return -ENODEV; part = disk_get_part(disk, partno); if (!strcmp(part->info->uuid, AVM_PART_TFFS1_UUID)) { dev_info(&mtd->dev, "tffs partition found\n"); TFFS3_Register_NAND(mtd); if (mtd->avm_sync_io_init) TFFS3_Register_Panic_CB(mtd, mtd->avm_sync_io_init); else dev_warn(&mtd->dev, "device has no panic-mode support\n"); result = 0; } disk_put_part(part); return result; } int avm_prom_config_server_getloc(struct avm_prom_config_loc *loc) { if (!smp_load_acquire(&avm_prom_server_ready)) { pr_err("%s: No backend device (yet)\n", __func__); return -ENODEV; } pr_info("Server probed.\n"); *loc = avm_prom_mtd.prom_dev.loc; return 0; } static int avm_prom_server_raw_read(struct avm_prom_config_device *prom_dev, loff_t offset, void *buffer, size_t len) { return avm_prom_raw_read_config(offset, buffer, len); } static const struct avm_prom_config_device_ops avm_prom_server_ops = { .read = avm_prom_server_raw_read }; struct avm_prom_config_device avm_prom_server_device = { .ops = &avm_prom_server_ops, }; static void mtd_add_notifier(struct mtd_info *mtd) { struct pdr_image_dir *pdr, *oemd; size_t readlen, pdr_size, cfg_size; struct avm_prom_config_loc *loc; loff_t cfg_start; unsigned int idx; int result; result = 0; if (!mtd_add_tffs(mtd)) return; pdr_size = sizeof(*pdr) * PDR_IMAGE_DIR_MAX_CNT; pdr = vmalloc(pdr_size); if (pdr == NULL) { result = -ENOMEM; pr_err("Error reading PDR: %d\n", result); goto err_out; } if (avm_prom_mtd.mtd != NULL || strcmp(mtd->name, PLATFORM_MTD)) goto err_out; result = mtd_read(mtd, 0, pdr_size, &readlen, (u_char *)pdr); if (result != 0 || readlen != pdr_size || le32_to_cpu(pdr[0].id) != PDR_IMAGE_ID_IMG_DIR) { dev_warn(&mtd->dev, "No valid PDR in mtd\n"); result = result ?: -EILSEQ; goto err_out; } dev_info(&mtd->dev, "Found valid PDR\n"); oemd = NULL; for (idx = 0; idx < PDR_IMAGE_DIR_MAX_CNT; ++idx) { uint32_t id = le32_to_cpu(pdr[idx].id); if (id == PDR_IMAGE_ID_END) break; if (id == PDR_IMAGE_ID_OEMD0) { oemd = &pdr[idx]; break; } } if (!oemd) { dev_err(&mtd->dev, "No OEM data found\n"); result = -EINVAL; goto err_out; } cfg_start = le32_to_cpu(oemd->offset); cfg_size = le32_to_cpu(oemd->size); if (cfg_start < pdr_size || cfg_start + cfg_size > mtd->size) { dev_warn(&mtd->dev, "Bogus PDR in mtd\n"); result = -EILSEQ; goto err_out; } dev_info(&mtd->dev, "Found valid OEM data\n"); avm_prom_mtd.mtd = mtd; loc = &avm_prom_mtd.prom_dev.loc; loc->offset = cfg_start; loc->len = cfg_size; loc->align_at = mtd->erasesize; pr_devel("Config data located at: ofs=%u len=%u align=%u\n", loc->offset, loc->len, loc->align_at); result = avm_prom_config_add_mtd_device(&avm_prom_mtd); if (result) { pr_err("Error adding backend device: %d\n", result); goto err_out; } /* Enable prom server */ smp_store_release(&avm_prom_server_ready, true); result = avm_prom_load_config_entries(); if (result) { pr_err("Error loading config entries: %d\n", result); goto err_out; } if (avm_prom_is_config_available(FINAL)) restore_finalization(FINAL); if (avm_prom_is_config_available(PRODENV)) restore_finalization(PRODENV); err_out: vfree(pdr); } static void mtd_rm_notifier(struct mtd_info *mtd) { pr_debug("ignoring mtd %s\n", mtd->name); } struct mtd_notifier puma_mtd_notifier = { .add = mtd_add_notifier, .remove = mtd_rm_notifier }; int __init puma_mtd_init(void) { pr_debug("Registering Platform Data MTD notifier\n"); register_mtd_user(&puma_mtd_notifier); return 0; } fs_initcall(puma_mtd_init);