diff options
Diffstat (limited to 'drivers/fpga')
| -rw-r--r-- | drivers/fpga/Kconfig | 20 | ||||
| -rw-r--r-- | drivers/fpga/Makefile | 4 | ||||
| -rw-r--r-- | drivers/fpga/altera-pr-ip-core.c | 2 | ||||
| -rw-r--r-- | drivers/fpga/dfl.c | 4 | ||||
| -rw-r--r-- | drivers/fpga/fpga-bridge.c | 6 | ||||
| -rw-r--r-- | drivers/fpga/fpga-mgr.c | 229 | ||||
| -rw-r--r-- | drivers/fpga/fpga-region.c | 6 | ||||
| -rw-r--r-- | drivers/fpga/intel-m10-bmc-sec-update.c | 625 | ||||
| -rw-r--r-- | drivers/fpga/microchip-spi.c | 398 |
9 files changed, 1257 insertions, 37 deletions
diff --git a/drivers/fpga/Kconfig b/drivers/fpga/Kconfig index 991b3f361ec9..6c416955da53 100644 --- a/drivers/fpga/Kconfig +++ b/drivers/fpga/Kconfig @@ -243,4 +243,24 @@ config FPGA_MGR_VERSAL_FPGA configure the programmable logic(PL). To compile this as a module, choose M here. + +config FPGA_M10_BMC_SEC_UPDATE + tristate "Intel MAX10 BMC Secure Update driver" + depends on MFD_INTEL_M10_BMC && FW_UPLOAD + help + Secure update support for the Intel MAX10 board management + controller. + + This is a subdriver of the Intel MAX10 board management controller + (BMC) and provides support for secure updates for the BMC image, + the FPGA image, the Root Entry Hashes, etc. + +config FPGA_MGR_MICROCHIP_SPI + tristate "Microchip Polarfire SPI FPGA manager" + depends on SPI + help + FPGA manager driver support for Microchip Polarfire FPGAs + programming over slave SPI interface with .dat formatted + bitstream image. + endif # FPGA diff --git a/drivers/fpga/Makefile b/drivers/fpga/Makefile index 5935b3d0abd5..42ae8b58abce 100644 --- a/drivers/fpga/Makefile +++ b/drivers/fpga/Makefile @@ -19,9 +19,13 @@ obj-$(CONFIG_FPGA_MGR_XILINX_SPI) += xilinx-spi.o obj-$(CONFIG_FPGA_MGR_ZYNQ_FPGA) += zynq-fpga.o obj-$(CONFIG_FPGA_MGR_ZYNQMP_FPGA) += zynqmp-fpga.o obj-$(CONFIG_FPGA_MGR_VERSAL_FPGA) += versal-fpga.o +obj-$(CONFIG_FPGA_MGR_MICROCHIP_SPI) += microchip-spi.o obj-$(CONFIG_ALTERA_PR_IP_CORE) += altera-pr-ip-core.o obj-$(CONFIG_ALTERA_PR_IP_CORE_PLAT) += altera-pr-ip-core-plat.o +# FPGA Secure Update Drivers +obj-$(CONFIG_FPGA_M10_BMC_SEC_UPDATE) += intel-m10-bmc-sec-update.o + # FPGA Bridge Drivers obj-$(CONFIG_FPGA_BRIDGE) += fpga-bridge.o obj-$(CONFIG_SOCFPGA_FPGA_BRIDGE) += altera-hps2fpga.o altera-fpga2sdram.o diff --git a/drivers/fpga/altera-pr-ip-core.c b/drivers/fpga/altera-pr-ip-core.c index be0667968d33..df8671af4a92 100644 --- a/drivers/fpga/altera-pr-ip-core.c +++ b/drivers/fpga/altera-pr-ip-core.c @@ -108,7 +108,7 @@ static int alt_pr_fpga_write(struct fpga_manager *mgr, const char *buf, u32 *buffer_32 = (u32 *)buf; size_t i = 0; - if (count <= 0) + if (!count) return -EINVAL; /* Write out the complete 32-bit chunks */ diff --git a/drivers/fpga/dfl.c b/drivers/fpga/dfl.c index 6bff39ff21a0..5498bc337f8b 100644 --- a/drivers/fpga/dfl.c +++ b/drivers/fpga/dfl.c @@ -342,7 +342,7 @@ static void release_dfl_dev(struct device *dev) if (ddev->mmio_res.parent) release_resource(&ddev->mmio_res); - ida_simple_remove(&dfl_device_ida, ddev->id); + ida_free(&dfl_device_ida, ddev->id); kfree(ddev->irqs); kfree(ddev); } @@ -360,7 +360,7 @@ dfl_dev_add(struct dfl_feature_platform_data *pdata, if (!ddev) return ERR_PTR(-ENOMEM); - id = ida_simple_get(&dfl_device_ida, 0, 0, GFP_KERNEL); + id = ida_alloc(&dfl_device_ida, GFP_KERNEL); if (id < 0) { dev_err(&pdev->dev, "unable to get id\n"); kfree(ddev); diff --git a/drivers/fpga/fpga-bridge.c b/drivers/fpga/fpga-bridge.c index 16f2b164a178..727704431f61 100644 --- a/drivers/fpga/fpga-bridge.c +++ b/drivers/fpga/fpga-bridge.c @@ -342,7 +342,7 @@ fpga_bridge_register(struct device *parent, const char *name, if (!bridge) return ERR_PTR(-ENOMEM); - id = ida_simple_get(&fpga_bridge_ida, 0, 0, GFP_KERNEL); + id = ida_alloc(&fpga_bridge_ida, GFP_KERNEL); if (id < 0) { ret = id; goto error_kfree; @@ -375,7 +375,7 @@ fpga_bridge_register(struct device *parent, const char *name, return bridge; error_device: - ida_simple_remove(&fpga_bridge_ida, id); + ida_free(&fpga_bridge_ida, id); error_kfree: kfree(bridge); @@ -407,7 +407,7 @@ static void fpga_bridge_dev_release(struct device *dev) { struct fpga_bridge *bridge = to_fpga_bridge(dev); - ida_simple_remove(&fpga_bridge_ida, bridge->dev.id); + ida_free(&fpga_bridge_ida, bridge->dev.id); kfree(bridge); } diff --git a/drivers/fpga/fpga-mgr.c b/drivers/fpga/fpga-mgr.c index a3595ecc3f79..8efa67620e21 100644 --- a/drivers/fpga/fpga-mgr.c +++ b/drivers/fpga/fpga-mgr.c @@ -74,6 +74,15 @@ static inline int fpga_mgr_write_complete(struct fpga_manager *mgr, return 0; } +static inline int fpga_mgr_parse_header(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + if (mgr->mops->parse_header) + return mgr->mops->parse_header(mgr, info, buf, count); + return 0; +} + static inline int fpga_mgr_write_init(struct fpga_manager *mgr, struct fpga_image_info *info, const char *buf, size_t count) @@ -136,24 +145,141 @@ void fpga_image_info_free(struct fpga_image_info *info) EXPORT_SYMBOL_GPL(fpga_image_info_free); /* - * Call the low level driver's write_init function. This will do the + * Call the low level driver's parse_header function with entire FPGA image + * buffer on the input. This will set info->header_size and info->data_size. + */ +static int fpga_mgr_parse_header_mapped(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + int ret; + + mgr->state = FPGA_MGR_STATE_PARSE_HEADER; + ret = fpga_mgr_parse_header(mgr, info, buf, count); + + if (info->header_size + info->data_size > count) { + dev_err(&mgr->dev, "Bitstream data outruns FPGA image\n"); + ret = -EINVAL; + } + + if (ret) { + dev_err(&mgr->dev, "Error while parsing FPGA image header\n"); + mgr->state = FPGA_MGR_STATE_PARSE_HEADER_ERR; + } + + return ret; +} + +/* + * Call the low level driver's parse_header function with first fragment of + * scattered FPGA image on the input. If header fits first fragment, + * parse_header will set info->header_size and info->data_size. If it is not, + * parse_header will set desired size to info->header_size and -EAGAIN will be + * returned. + */ +static int fpga_mgr_parse_header_sg_first(struct fpga_manager *mgr, + struct fpga_image_info *info, + struct sg_table *sgt) +{ + struct sg_mapping_iter miter; + int ret; + + mgr->state = FPGA_MGR_STATE_PARSE_HEADER; + + sg_miter_start(&miter, sgt->sgl, sgt->nents, SG_MITER_FROM_SG); + if (sg_miter_next(&miter) && + miter.length >= info->header_size) + ret = fpga_mgr_parse_header(mgr, info, miter.addr, miter.length); + else + ret = -EAGAIN; + sg_miter_stop(&miter); + + if (ret && ret != -EAGAIN) { + dev_err(&mgr->dev, "Error while parsing FPGA image header\n"); + mgr->state = FPGA_MGR_STATE_PARSE_HEADER_ERR; + } + + return ret; +} + +/* + * Copy scattered FPGA image fragments to temporary buffer and call the + * low level driver's parse_header function. This should be called after + * fpga_mgr_parse_header_sg_first() returned -EAGAIN. In case of success, + * pointer to the newly allocated image header copy will be returned and + * its size will be set into *ret_size. Returned buffer needs to be freed. + */ +static void *fpga_mgr_parse_header_sg(struct fpga_manager *mgr, + struct fpga_image_info *info, + struct sg_table *sgt, size_t *ret_size) +{ + size_t len, new_header_size, header_size = 0; + char *new_buf, *buf = NULL; + int ret; + + do { + new_header_size = info->header_size; + if (new_header_size <= header_size) { + dev_err(&mgr->dev, "Requested invalid header size\n"); + ret = -EFAULT; + break; + } + + new_buf = krealloc(buf, new_header_size, GFP_KERNEL); + if (!new_buf) { + ret = -ENOMEM; + break; + } + + buf = new_buf; + + len = sg_pcopy_to_buffer(sgt->sgl, sgt->nents, + buf + header_size, + new_header_size - header_size, + header_size); + if (len != new_header_size - header_size) { + ret = -EFAULT; + break; + } + + header_size = new_header_size; + ret = fpga_mgr_parse_header(mgr, info, buf, header_size); + } while (ret == -EAGAIN); + + if (ret) { + dev_err(&mgr->dev, "Error while parsing FPGA image header\n"); + mgr->state = FPGA_MGR_STATE_PARSE_HEADER_ERR; + kfree(buf); + buf = ERR_PTR(ret); + } + + *ret_size = header_size; + + return buf; +} + +/* + * Call the low level driver's write_init function. This will do the * device-specific things to get the FPGA into the state where it is ready to - * receive an FPGA image. The low level driver only gets to see the first - * initial_header_size bytes in the buffer. + * receive an FPGA image. The low level driver gets to see at least first + * info->header_size bytes in the buffer. If info->header_size is 0, + * write_init will not get any bytes of image buffer. */ static int fpga_mgr_write_init_buf(struct fpga_manager *mgr, struct fpga_image_info *info, const char *buf, size_t count) { + size_t header_size = info->header_size; int ret; mgr->state = FPGA_MGR_STATE_WRITE_INIT; - if (!mgr->mops->initial_header_size) { + + if (header_size > count) + ret = -EINVAL; + else if (!header_size) ret = fpga_mgr_write_init(mgr, info, NULL, 0); - } else { - count = min(mgr->mops->initial_header_size, count); + else ret = fpga_mgr_write_init(mgr, info, buf, count); - } if (ret) { dev_err(&mgr->dev, "Error preparing FPGA for writing\n"); @@ -164,39 +290,50 @@ static int fpga_mgr_write_init_buf(struct fpga_manager *mgr, return 0; } -static int fpga_mgr_write_init_sg(struct fpga_manager *mgr, - struct fpga_image_info *info, - struct sg_table *sgt) +static int fpga_mgr_prepare_sg(struct fpga_manager *mgr, + struct fpga_image_info *info, + struct sg_table *sgt) { struct sg_mapping_iter miter; size_t len; char *buf; int ret; - if (!mgr->mops->initial_header_size) + /* Short path. Low level driver don't care about image header. */ + if (!mgr->mops->initial_header_size && !mgr->mops->parse_header) return fpga_mgr_write_init_buf(mgr, info, NULL, 0); /* * First try to use miter to map the first fragment to access the * header, this is the typical path. */ - sg_miter_start(&miter, sgt->sgl, sgt->nents, SG_MITER_FROM_SG); - if (sg_miter_next(&miter) && - miter.length >= mgr->mops->initial_header_size) { - ret = fpga_mgr_write_init_buf(mgr, info, miter.addr, - miter.length); + ret = fpga_mgr_parse_header_sg_first(mgr, info, sgt); + /* If 0, header fits first fragment, call write_init on it */ + if (!ret) { + sg_miter_start(&miter, sgt->sgl, sgt->nents, SG_MITER_FROM_SG); + if (sg_miter_next(&miter)) { + ret = fpga_mgr_write_init_buf(mgr, info, miter.addr, + miter.length); + sg_miter_stop(&miter); + return ret; + } sg_miter_stop(&miter); + /* + * If -EAGAIN, more sg buffer is needed, + * otherwise an error has occurred. + */ + } else if (ret != -EAGAIN) { return ret; } - sg_miter_stop(&miter); - /* Otherwise copy the fragments into temporary memory. */ - buf = kmalloc(mgr->mops->initial_header_size, GFP_KERNEL); - if (!buf) - return -ENOMEM; + /* + * Copy the fragments into temporary memory. + * Copying is done inside fpga_mgr_parse_header_sg(). + */ + buf = fpga_mgr_parse_header_sg(mgr, info, sgt, &len); + if (IS_ERR(buf)) + return PTR_ERR(buf); - len = sg_copy_to_buffer(sgt->sgl, sgt->nents, buf, - mgr->mops->initial_header_size); ret = fpga_mgr_write_init_buf(mgr, info, buf, len); kfree(buf); @@ -227,7 +364,7 @@ static int fpga_mgr_buf_load_sg(struct fpga_manager *mgr, { int ret; - ret = fpga_mgr_write_init_sg(mgr, info, sgt); + ret = fpga_mgr_prepare_sg(mgr, info, sgt); if (ret) return ret; @@ -236,17 +373,35 @@ static int fpga_mgr_buf_load_sg(struct fpga_manager *mgr, if (mgr->mops->write_sg) { ret = fpga_mgr_write_sg(mgr, sgt); } else { + size_t length, count = 0, data_size = info->data_size; struct sg_mapping_iter miter; sg_miter_start(&miter, sgt->sgl, sgt->nents, SG_MITER_FROM_SG); + + if (mgr->mops->skip_header && + !sg_miter_skip(&miter, info->header_size)) { + ret = -EINVAL; + goto out; + } + while (sg_miter_next(&miter)) { - ret = fpga_mgr_write(mgr, miter.addr, miter.length); + if (data_size) + length = min(miter.length, data_size - count); + else + length = miter.length; + + ret = fpga_mgr_write(mgr, miter.addr, length); if (ret) break; + + count += length; + if (data_size && count >= data_size) + break; } sg_miter_stop(&miter); } +out: if (ret) { dev_err(&mgr->dev, "Error while writing image data to FPGA\n"); mgr->state = FPGA_MGR_STATE_WRITE_ERR; @@ -262,10 +417,22 @@ static int fpga_mgr_buf_load_mapped(struct fpga_manager *mgr, { int ret; + ret = fpga_mgr_parse_header_mapped(mgr, info, buf, count); + if (ret) + return ret; + ret = fpga_mgr_write_init_buf(mgr, info, buf, count); if (ret) return ret; + if (mgr->mops->skip_header) { + buf += info->header_size; + count -= info->header_size; + } + + if (info->data_size) + count = info->data_size; + /* * Write the FPGA image to the FPGA. */ @@ -404,6 +571,8 @@ static int fpga_mgr_firmware_load(struct fpga_manager *mgr, */ int fpga_mgr_load(struct fpga_manager *mgr, struct fpga_image_info *info) { + info->header_size = mgr->mops->initial_header_size; + if (info->sgt) return fpga_mgr_buf_load_sg(mgr, info, info->sgt); if (info->buf && info->count) @@ -424,6 +593,10 @@ static const char * const state_str[] = { [FPGA_MGR_STATE_FIRMWARE_REQ] = "firmware request", [FPGA_MGR_STATE_FIRMWARE_REQ_ERR] = "firmware request error", + /* Parse FPGA image header */ + [FPGA_MGR_STATE_PARSE_HEADER] = "parse header", + [FPGA_MGR_STATE_PARSE_HEADER_ERR] = "parse header error", + /* Preparing FPGA to receive image */ [FPGA_MGR_STATE_WRITE_INIT] = "write init", [FPGA_MGR_STATE_WRITE_INIT_ERR] = "write init error", @@ -623,7 +796,7 @@ fpga_mgr_register_full(struct device *parent, const struct fpga_manager_info *in if (!mgr) return ERR_PTR(-ENOMEM); - id = ida_simple_get(&fpga_mgr_ida, 0, 0, GFP_KERNEL); + id = ida_alloc(&fpga_mgr_ida, GFP_KERNEL); if (id < 0) { ret = id; goto error_kfree; @@ -662,7 +835,7 @@ fpga_mgr_register_full(struct device *parent, const struct fpga_manager_info *in return mgr; error_device: - ida_simple_remove(&fpga_mgr_ida, id); + ida_free(&fpga_mgr_ida, id); error_kfree: kfree(mgr); @@ -790,7 +963,7 @@ static void fpga_mgr_dev_release(struct device *dev) { struct fpga_manager *mgr = to_fpga_manager(dev); - ida_simple_remove(&fpga_mgr_ida, mgr->dev.id); + ida_free(&fpga_mgr_ida, mgr->dev.id); kfree(mgr); } diff --git a/drivers/fpga/fpga-region.c b/drivers/fpga/fpga-region.c index 485948e3c0db..27ff9dea04ae 100644 --- a/drivers/fpga/fpga-region.c +++ b/drivers/fpga/fpga-region.c @@ -202,7 +202,7 @@ fpga_region_register_full(struct device *parent, const struct fpga_region_info * if (!region) return ERR_PTR(-ENOMEM); - id = ida_simple_get(&fpga_region_ida, 0, 0, GFP_KERNEL); + id = ida_alloc(&fpga_region_ida, GFP_KERNEL); if (id < 0) { ret = id; goto err_free; @@ -234,7 +234,7 @@ fpga_region_register_full(struct device *parent, const struct fpga_region_info * return region; err_remove: - ida_simple_remove(&fpga_region_ida, id); + ida_free(&fpga_region_ida, id); err_free: kfree(region); @@ -283,7 +283,7 @@ static void fpga_region_dev_release(struct device *dev) { struct fpga_region *region = to_fpga_region(dev); - ida_simple_remove(&fpga_region_ida, region->dev.id); + ida_free(&fpga_region_ida, region->dev.id); kfree(region); } diff --git a/drivers/fpga/intel-m10-bmc-sec-update.c b/drivers/fpga/intel-m10-bmc-sec-update.c new file mode 100644 index 000000000000..72c677c910de --- /dev/null +++ b/drivers/fpga/intel-m10-bmc-sec-update.c @@ -0,0 +1,625 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel MAX10 Board Management Controller Secure Update Driver + * + * Copyright (C) 2019-2022 Intel Corporation. All rights reserved. + * + */ +#include <linux/bitfield.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/mfd/intel-m10-bmc.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +struct m10bmc_sec { + struct device *dev; + struct intel_m10bmc *m10bmc; + struct fw_upload *fwl; + char *fw_name; + u32 fw_name_id; + bool cancel_request; +}; + +static DEFINE_XARRAY_ALLOC(fw_upload_xa); + +/* Root Entry Hash (REH) support */ +#define REH_SHA256_SIZE 32 +#define REH_SHA384_SIZE 48 +#define REH_MAGIC GENMASK(15, 0) +#define REH_SHA_NUM_BYTES GENMASK(31, 16) + +static ssize_t +show_root_entry_hash(struct device *dev, u32 exp_magic, + u32 prog_addr, u32 reh_addr, char *buf) +{ + struct m10bmc_sec *sec = dev_get_drvdata(dev); + int sha_num_bytes, i, ret, cnt = 0; + u8 hash[REH_SHA384_SIZE]; + unsigned int stride; + u32 magic; + + stride = regmap_get_reg_stride(sec->m10bmc->regmap); + ret = m10bmc_raw_read(sec->m10bmc, prog_addr, &magic); + if (ret) + return ret; + + if (FIELD_GET(REH_MAGIC, magic) != exp_magic) + return sysfs_emit(buf, "hash not programmed\n"); + + sha_num_bytes = FIELD_GET(REH_SHA_NUM_BYTES, magic) / 8; + if ((sha_num_bytes % stride) || + (sha_num_bytes != REH_SHA256_SIZE && + sha_num_bytes != REH_SHA384_SIZE)) { + dev_err(sec->dev, "%s bad sha num bytes %d\n", __func__, + sha_num_bytes); + return -EINVAL; + } + + ret = regmap_bulk_read(sec->m10bmc->regmap, reh_addr, + hash, sha_num_bytes / stride); + if (ret) { + dev_err(dev, "failed to read root entry hash: %x cnt %x: %d\n", + reh_addr, sha_num_bytes / stride, ret); + return ret; + } + + for (i = 0; i < sha_num_bytes; i++) + cnt += sprintf(buf + cnt, "%02x", hash[i]); + cnt += sprintf(buf + cnt, "\n"); + + return cnt; +} + +#define DEVICE_ATTR_SEC_REH_RO(_name, _magic, _prog_addr, _reh_addr) \ +static ssize_t _name##_root_entry_hash_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ return show_root_entry_hash(dev, _magic, _prog_addr, _reh_addr, buf); } \ +static DEVICE_ATTR_RO(_name##_root_entry_hash) + +DEVICE_ATTR_SEC_REH_RO(bmc, BMC_PROG_MAGIC, BMC_PROG_ADDR, BMC_REH_ADDR); +DEVICE_ATTR_SEC_REH_RO(sr, SR_PROG_MAGIC, SR_PROG_ADDR, SR_REH_ADDR); +DEVICE_ATTR_SEC_REH_RO(pr, PR_PROG_MAGIC, PR_PROG_ADDR, PR_REH_ADDR); + +#define CSK_BIT_LEN 128U +#define CSK_32ARRAY_SIZE DIV_ROUND_UP(CSK_BIT_LEN, 32) + +static ssize_t +show_canceled_csk(struct device *dev, u32 addr, char *buf) +{ + unsigned int i, stride, size = CSK_32ARRAY_SIZE * sizeof(u32); + struct m10bmc_sec *sec = dev_get_drvdata(dev); + DECLARE_BITMAP(csk_map, CSK_BIT_LEN); + __le32 csk_le32[CSK_32ARRAY_SIZE]; + u32 csk32[CSK_32ARRAY_SIZE]; + int ret; + + stride = regmap_get_reg_stride(sec->m10bmc->regmap); + if (size % stride) { + dev_err(sec->dev, + "CSK vector size (0x%x) not aligned to stride (0x%x)\n", + size, stride); + WARN_ON_ONCE(1); + return -EINVAL; + } + + ret = regmap_bulk_read(sec->m10bmc->regmap, addr, csk_le32, + size / stride); + if (ret) { + dev_err(sec->dev, "failed to read CSK vector: %x cnt %x: %d\n", + addr, size / stride, ret); + return ret; + } + + for (i = 0; i < CSK_32ARRAY_SIZE; i++) + csk32[i] = le32_to_cpu(((csk_le32[i]))); + + bitmap_from_arr32(csk_map, csk32, CSK_BIT_LEN); + bitmap_complement(csk_map, csk_map, CSK_BIT_LEN); + return bitmap_print_to_pagebuf(1, buf, csk_map, CSK_BIT_LEN); +} + +#define DEVICE_ATTR_SEC_CSK_RO(_name, _addr) \ +static ssize_t _name##_canceled_csks_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ return show_canceled_csk(dev, _addr, buf); } \ +static DEVICE_ATTR_RO(_name##_canceled_csks) + +#define CSK_VEC_OFFSET 0x34 + +DEVICE_ATTR_SEC_CSK_RO(bmc, BMC_PROG_ADDR + CSK_VEC_OFFSET); +DEVICE_ATTR_SEC_CSK_RO(sr, SR_PROG_ADDR + CSK_VEC_OFFSET); +DEVICE_ATTR_SEC_CSK_RO(pr, PR_PROG_ADDR + CSK_VEC_OFFSET); + +#define FLASH_COUNT_SIZE 4096 /* count stored as inverted bit vector */ + +static ssize_t flash_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct m10bmc_sec *sec = dev_get_drvdata(dev); + unsigned int stride, num_bits; + u8 *flash_buf; + int cnt, ret; + + stride = regmap_get_reg_stride(sec->m10bmc->regmap); + num_bits = FLASH_COUNT_SIZE * 8; + + flash_buf = kmalloc(FLASH_COUNT_SIZE, GFP_KERNEL); + if (!flash_buf) + return -ENOMEM; + + if (FLASH_COUNT_SIZE % stride) { + dev_err(sec->dev, + "FLASH_COUNT_SIZE (0x%x) not aligned to stride (0x%x)\n", + FLASH_COUNT_SIZE, stride); + WARN_ON_ONCE(1); + return -EINVAL; + } + + ret = regmap_bulk_read(sec->m10bmc->regmap, STAGING_FLASH_COUNT, + flash_buf, FLASH_COUNT_SIZE / stride); + if (ret) { + dev_err(sec->dev, + "failed to read flash count: %x cnt %x: %d\n", + STAGING_FLASH_COUNT, FLASH_COUNT_SIZE / stride, ret); + goto exit_free; + } + cnt = num_bits - bitmap_weight((unsigned long *)flash_buf, num_bits); + +exit_free: + kfree(flash_buf); + + return ret ? : sysfs_emit(buf, "%u\n", cnt); +} +static DEVICE_ATTR_RO(flash_count); + +static struct attribute *m10bmc_security_attrs[] = { + &dev_attr_flash_count.attr, + &dev_attr_bmc_root_entry_hash.attr, + &dev_attr_sr_root_entry_hash.attr, + &dev_attr_pr_root_entry_hash.attr, + &dev_attr_sr_canceled_csks.attr, + &dev_attr_pr_canceled_csks.attr, + &dev_attr_bmc_canceled_csks.attr, + NULL, +}; + +static struct attribute_group m10bmc_security_attr_group = { + .name = "security", + .attrs = m10bmc_security_attrs, +}; + +static const struct attribute_group *m10bmc_sec_attr_groups[] = { + &m10bmc_security_attr_group, + NULL, +}; + +static void log_error_regs(struct m10bmc_sec *sec, u32 doorbell) +{ + u32 auth_result; + + dev_err(sec->dev, "RSU error status: 0x%08x\n", doorbell); + + if (!m10bmc_sys_read(sec->m10bmc, M10BMC_AUTH_RESULT, &auth_result)) + dev_err(sec->dev, "RSU auth result: 0x%08x\n", auth_result); +} + +static enum fw_upload_err rsu_check_idle(struct m10bmc_sec *sec) +{ + u32 doorbell; + int ret; + + ret = m10bmc_sys_read(sec->m10bmc, M10BMC_DOORBELL, &doorbell); + if (ret) + return FW_UPLOAD_ERR_RW_ERROR; + + if (rsu_prog(doorbell) != RSU_PROG_IDLE && + rsu_prog(doorbell) != RSU_PROG_RSU_DONE) { + log_error_regs(sec, doorbell); + return FW_UPLOAD_ERR_BUSY; + } + + return FW_UPLOAD_ERR_NONE; +} + +static inline bool rsu_start_done(u32 doorbell) +{ + u32 status, progress; + + if (doorbell & DRBL_RSU_REQUEST) + return false; + + status = rsu_stat(doorbell); + if (status == RSU_STAT_ERASE_FAIL || status == RSU_STAT_WEAROUT) + return true; + + progress = rsu_prog(doorbell); + if (progress != RSU_PROG_IDLE && progress != RSU_PROG_RSU_DONE) + return true; + + return false; +} + +static enum fw_upload_err rsu_update_init(struct m10bmc_sec *sec) +{ + u32 doorbell, status; + int ret; + + ret = regmap_update_bits(sec->m10bmc->regmap, + M10BMC_SYS_BASE + M10BMC_DOORBELL, + DRBL_RSU_REQUEST | DRBL_HOST_STATUS, + DRBL_RSU_REQUEST | + FIELD_PREP(DRBL_HOST_STATUS, + HOST_STATUS_IDLE)); + if (ret) + return FW_UPLOAD_ERR_RW_ERROR; + + ret = regmap_read_poll_timeout(sec->m10bmc->regmap, + M10BMC_SYS_BASE + M10BMC_DOORBELL, + doorbell, + rsu_start_done(doorbell), + NIOS_HANDSHAKE_INTERVAL_US, + NIOS_HANDSHAKE_TIMEOUT_US); + + if (ret == -ETIMEDOUT) { + log_error_regs(sec, doorbell); + return FW_UPLOAD_ERR_TIMEOUT; + } else if (ret) { + return FW_UPLOAD_ERR_RW_ERROR; + } + + status = rsu_stat(doorbell); + if (status == RSU_STAT_WEAROUT) { + dev_warn(sec->dev, "Excessive flash update count detected\n"); + return FW_UPLOAD_ERR_WEAROUT; + } else if (status == RSU_STAT_ERASE_FAIL) { + log_error_regs(sec, doorbell); + return FW_UPLOAD_ERR_HW_ERROR; + } + + return FW_UPLOAD_ERR_NONE; +} + +static enum fw_upload_err rsu_prog_ready(struct m10bmc_sec *sec) +{ + unsigned long poll_timeout; + u32 doorbell, progress; + int ret; + + ret = m10bmc_sys_read(sec->m10bmc, M10BMC_DOORBELL, &doorbell); + if (ret) + return FW_UPLOAD_ERR_RW_ERROR; + + poll_timeout = jiffies + msecs_to_jiffies(RSU_PREP_TIMEOUT_MS); + while (rsu_prog(doorbell) == RSU_PROG_PREPARE) { + msleep(RSU_PREP_INTERVAL_MS); + if (time_after(jiffies, poll_timeout)) + break; + + ret = m10bmc_sys_read(sec->m10bmc, M10BMC_DOORBELL, &doorbell); + if (ret) + return FW_UPLOAD_ERR_RW_ERROR; + } + + progress = rsu_prog(doorbell); + if (progress == RSU_PROG_PREPARE) { + log_error_regs(sec, doorbell); + return FW_UPLOAD_ERR_TIMEOUT; + } else if (progress != RSU_PROG_READY) { + log_error_regs(sec, doorbell); + return FW_UPLOAD_ERR_HW_ERROR; + } + + return FW_UPLOAD_ERR_NONE; +} + +static enum fw_upload_err rsu_send_data(struct m10bmc_sec *sec) +{ + u32 doorbell; + int ret; + + ret = regmap_update_bits(sec->m10bmc->regmap, + M10BMC_SYS_BASE + M10BMC_DOORBELL, + DRBL_HOST_STATUS, + FIELD_PREP(DRBL_HOST_STATUS, + HOST_STATUS_WRITE_DONE)); + if (ret) + return FW_UPLOAD_ERR_RW_ERROR; + + ret = regmap_read_poll_timeout(sec->m10bmc->regmap, + M10BMC_SYS_BASE + M10BMC_DOORBELL, + doorbell, + rsu_prog(doorbell) != RSU_PROG_READY, + NIOS_HANDSHAKE_INTERVAL_US, + NIOS_HANDSHAKE_TIMEOUT_US); + + if (ret == -ETIMEDOUT) { + log_error_regs(sec, doorbell); + return FW_UPLOAD_ERR_TIMEOUT; + } else if (ret) { + return FW_UPLOAD_ERR_RW_ERROR; + } + + switch (rsu_stat(doorbell)) { + case RSU_STAT_NORMAL: + case RSU_STAT_NIOS_OK: + case RSU_STAT_USER_OK: + case RSU_STAT_FACTORY_OK: + break; + default: + log_error_regs(sec, doorbell); + return FW_UPLOAD_ERR_HW_ERROR; + } + + return FW_UPLOAD_ERR_NONE; +} + +static int rsu_check_complete(struct m10bmc_sec *sec, u32 *doorbell) +{ + if (m10bmc_sys_read(sec->m10bmc, M10BMC_DOORBELL, doorbell)) + return -EIO; + + switch (rsu_stat(*doorbell)) { + case RSU_STAT_NORMAL: + case RSU_STAT_NIOS_OK: + case RSU_STAT_USER_OK: + case RSU_STAT_FACTORY_OK: + break; + default: + return -EINVAL; + } + + switch (rsu_prog(*doorbell)) { + case RSU_PROG_IDLE: + case RSU_PROG_RSU_DONE: + return 0; + case RSU_PROG_AUTHENTICATING: + case RSU_PROG_COPYING: + case RSU_PROG_UPDATE_CANCEL: + case RSU_PROG_PROGRAM_KEY_HASH: + return -EAGAIN; + default: + return -EINVAL; + } +} + +static enum fw_upload_err rsu_cancel(struct m10bmc_sec *sec) +{ + u32 doorbell; + int ret; + + ret = m10bmc_sys_read(sec->m10bmc, M10BMC_DOORBELL, &doorbell); + if (ret) + return FW_UPLOAD_ERR_RW_ERROR; + + if (rsu_prog(doorbell) != RSU_PROG_READY) + return FW_UPLOAD_ERR_BUSY; + + ret = regmap_update_bits(sec->m10bmc->regmap, + M10BMC_SYS_BASE + M10BMC_DOORBELL, + DRBL_HOST_STATUS, + FIELD_PREP(DRBL_HOST_STATUS, + HOST_STATUS_ABORT_RSU)); + if (ret) + return FW_UPLOAD_ERR_RW_ERROR; + + return FW_UPLOAD_ERR_CANCELED; +} + +static enum fw_upload_err m10bmc_sec_prepare(struct fw_upload *fwl, + const u8 *data, u32 size) +{ + struct m10bmc_sec *sec = fwl->dd_handle; + u32 ret; + + sec->cancel_request = false; + + if (!size || size > M10BMC_STAGING_SIZE) + return FW_UPLOAD_ERR_INVALID_SIZE; + + ret = rsu_check_idle(sec); + if (ret != FW_UPLOAD_ERR_NONE) + return ret; + + ret = rsu_update_init(sec); + if (ret != FW_UPLOAD_ERR_NONE) + return ret; + + ret = rsu_prog_ready(sec); + if (ret != FW_UPLOAD_ERR_NONE) + return ret; + + if (sec->cancel_request) + return rsu_cancel(sec); + + return FW_UPLOAD_ERR_NONE; +} + +#define WRITE_BLOCK_SIZE 0x4000 /* Default write-block size is 0x4000 bytes */ + +static enum fw_upload_err m10bmc_sec_write(struct fw_upload *fwl, const u8 *data, + u32 offset, u32 size, u32 *written) +{ + struct m10bmc_sec *sec = fwl->dd_handle; + u32 blk_size, doorbell, extra_offset; + unsigned int stride, extra = 0; + int ret; + + stride = regmap_get_reg_stride(sec->m10bmc->regmap); + if (sec->cancel_request) + return rsu_cancel(sec); + + ret = m10bmc_sys_read(sec->m10bmc, M10BMC_DOORBELL, &doorbell); + if (ret) { + return FW_UPLOAD_ERR_RW_ERROR; + } else if (rsu_prog(doorbell) != RSU_PROG_READY) { + log_error_regs(sec, doorbell); + return FW_UPLOAD_ERR_HW_ERROR; + } + + WARN_ON_ONCE(WRITE_BLOCK_SIZE % stride); + blk_size = min_t(u32, WRITE_BLOCK_SIZE, size); + ret = regmap_bulk_write(sec->m10bmc->regmap, + M10BMC_STAGING_BASE + offset, + (void *)data + offset, + blk_size / stride); + if (ret) + return FW_UPLOAD_ERR_RW_ERROR; + + /* + * If blk_size is not aligned to stride, then handle the extra + * bytes with regmap_write. + */ + if (blk_size % stride) { + extra_offset = offset + ALIGN_DOWN(blk_size, stride); + memcpy(&extra, (u8 *)(data + extra_offset), blk_size % stride); + ret = regmap_write(sec->m10bmc->regmap, + M10BMC_STAGING_BASE + extra_offset, extra); + if (ret) + return FW_UPLOAD_ERR_RW_ERROR; + } + + *written = blk_size; + return FW_UPLOAD_ERR_NONE; +} + +static enum fw_upload_err m10bmc_sec_poll_complete(struct fw_upload *fwl) +{ + struct m10bmc_sec *sec = fwl->dd_handle; + unsigned long poll_timeout; + u32 doorbell, result; + int ret; + + if (sec->cancel_request) + return rsu_cancel(sec); + + result = rsu_send_data(sec); + if (result != FW_UPLOAD_ERR_NONE) + return result; + + poll_timeout = jiffies + msecs_to_jiffies(RSU_COMPLETE_TIMEOUT_MS); + do { + msleep(RSU_COMPLETE_INTERVAL_MS); + ret = rsu_check_complete(sec, &doorbell); + } while (ret == -EAGAIN && !time_after(jiffies, poll_timeout)); + + if (ret == -EAGAIN) { + log_error_regs(sec, doorbell); + return FW_UPLOAD_ERR_TIMEOUT; + } else if (ret == -EIO) { + return FW_UPLOAD_ERR_RW_ERROR; + } else if (ret) { + log_error_regs(sec, doorbell); + return FW_UPLOAD_ERR_HW_ERROR; + } + + return FW_UPLOAD_ERR_NONE; +} + +/* + * m10bmc_sec_cancel() may be called asynchronously with an on-going update. + * All other functions are called sequentially in a single thread. To avoid + * contention on register accesses, m10bmc_sec_cancel() must only update + * the cancel_request flag. Other functions will check this flag and handle + * the cancel request synchronously. + */ +static void m10bmc_sec_cancel(struct fw_upload *fwl) +{ + struct m10bmc_sec *sec = fwl->dd_handle; + + sec->cancel_request = true; +} + +static void m10bmc_sec_cleanup(struct fw_upload *fwl) +{ + struct m10bmc_sec *sec = fwl->dd_handle; + + (void)rsu_cancel(sec); +} +< |
