diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2022-08-04 20:05:48 +0200 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2022-08-04 20:05:48 +0200 |
commit | 228dfe98a313f6b6bff5da8b2c5e650e297ebf1a (patch) | |
tree | 501dc44c2453743eb2298fe23d6cdd0b5e91b2c2 /drivers/fpga | |
parent | drm/amd/display: restore code for plane with no modifiers (diff) | |
parent | drivers: lkdtm: fix clang -Wformat warning (diff) | |
download | linux-228dfe98a313f6b6bff5da8b2c5e650e297ebf1a.tar.xz linux-228dfe98a313f6b6bff5da8b2c5e650e297ebf1a.zip |
Merge tag 'char-misc-6.0-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc
Pull char / misc driver updates from Greg KH:
"Here is the large set of char and misc and other driver subsystem
changes for 6.0-rc1.
Highlights include:
- large set of IIO driver updates, additions, and cleanups
- new habanalabs device support added (loads of register maps much
like GPUs have)
- soundwire driver updates
- phy driver updates
- slimbus driver updates
- tiny virt driver fixes and updates
- misc driver fixes and updates
- interconnect driver updates
- hwtracing driver updates
- fpga driver updates
- extcon driver updates
- firmware driver updates
- counter driver update
- mhi driver fixes and updates
- binder driver fixes and updates
- speakup driver fixes
All of these have been in linux-next for a while without any reported
problems"
* tag 'char-misc-6.0-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc: (634 commits)
drivers: lkdtm: fix clang -Wformat warning
char: remove VR41XX related char driver
misc: Mark MICROCODE_MINOR unused
spmi: trace: fix stack-out-of-bound access in SPMI tracing functions
dt-bindings: iio: adc: Add compatible for MT8188
iio: light: isl29028: Fix the warning in isl29028_remove()
iio: accel: sca3300: Extend the trigger buffer from 16 to 32 bytes
iio: fix iio_format_avail_range() printing for none IIO_VAL_INT
iio: adc: max1027: unlock on error path in max1027_read_single_value()
iio: proximity: sx9324: add empty line in front of bullet list
iio: magnetometer: hmc5843: Remove duplicate 'the'
iio: magn: yas530: Use DEFINE_RUNTIME_DEV_PM_OPS() and pm_ptr() macros
iio: magnetometer: ak8974: Use DEFINE_RUNTIME_DEV_PM_OPS() and pm_ptr() macros
iio: light: veml6030: Use DEFINE_RUNTIME_DEV_PM_OPS() and pm_ptr() macros
iio: light: vcnl4035: Use DEFINE_RUNTIME_DEV_PM_OPS() and pm_ptr() macros
iio: light: vcnl4000: Use DEFINE_RUNTIME_DEV_PM_OPS() and pm_ptr() macros
iio: light: tsl2591: Use DEFINE_RUNTIME_DEV_PM_OPS() and pm_ptr()
iio: light: tsl2583: Use DEFINE_RUNTIME_DEV_PM_OPS and pm_ptr()
iio: light: isl29028: Use DEFINE_RUNTIME_DEV_PM_OPS() and pm_ptr()
iio: light: gp2ap002: Switch to DEFINE_RUNTIME_DEV_PM_OPS and pm_ptr()
...
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); +} + +static const struct fw_upload_ops m10bmc_ops = { + .prepare = m10bmc_sec_prepare, + .write = m10bmc_sec_write, + .poll_complete = m10bmc_sec_poll_complete, + .cancel = m10bmc_sec_cancel, + .cleanup = m10bmc_sec_cleanup, +}; + +#define SEC_UPDATE_LEN_MAX 32 +static int m10bmc_sec_probe(struct platform_device *pdev) +{ + char buf[SEC_UPDATE_LEN_MAX]; + struct m10bmc_sec *sec; + struct fw_upload *fwl; + unsigned int len; + int ret; + + sec = devm_kzalloc(&pdev->dev, sizeof(*sec), GFP_KERNEL); + if (!sec) + return -ENOMEM; + + sec->dev = &pdev->dev; + sec->m10bmc = dev_get_drvdata(pdev->dev.parent); + dev_set_drvdata(&pdev->dev, sec); + + ret = xa_alloc(&fw_upload_xa, &sec->fw_name_id, sec, + xa_limit_32b, GFP_KERNEL); + if (ret) + return ret; + + len = scnprintf(buf, SEC_UPDATE_LEN_MAX, "secure-update%d", + sec->fw_name_id); + sec->fw_name = kmemdup_nul(buf, len, GFP_KERNEL); + if (!sec->fw_name) + return -ENOMEM; + + fwl = firmware_upload_register(THIS_MODULE, sec->dev, sec->fw_name, + &m10bmc_ops, sec); + if (IS_ERR(fwl)) { + dev_err(sec->dev, "Firmware Upload driver failed to start\n"); + kfree(sec->fw_name); + xa_erase(&fw_upload_xa, sec->fw_name_id); + return PTR_ERR(fwl); + } + + sec->fwl = fwl; + return 0; +} + +static int m10bmc_sec_remove(struct platform_device *pdev) +{ + struct m10bmc_sec *sec = dev_get_drvdata(&pdev->dev); + + firmware_upload_unregister(sec->fwl); + kfree(sec->fw_name); + xa_erase(&fw_upload_xa, sec->fw_name_id); + + return 0; +} + +static const struct platform_device_id intel_m10bmc_sec_ids[] = { + { + .name = "n3000bmc-sec-update", + }, + { } +}; +MODULE_DEVICE_TABLE(platform, intel_m10bmc_sec_ids); + +static struct platform_driver intel_m10bmc_sec_driver = { + .probe = m10bmc_sec_probe, + .remove = m10bmc_sec_remove, + .driver = { + .name = "intel-m10bmc-sec-update", + .dev_groups = m10bmc_sec_attr_groups, + }, + .id_table = intel_m10bmc_sec_ids, +}; +module_platform_driver(intel_m10bmc_sec_driver); + +MODULE_AUTHOR("Intel Corporation"); +MODULE_DESCRIPTION("Intel MAX10 BMC Secure Update"); +MODULE_LICENSE("GPL"); diff --git a/drivers/fpga/microchip-spi.c b/drivers/fpga/microchip-spi.c new file mode 100644 index 000000000000..bd284c7b8dc9 --- /dev/null +++ b/drivers/fpga/microchip-spi.c @@ -0,0 +1,398 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Microchip Polarfire FPGA programming over slave SPI interface. + */ + +#include <asm/unaligned.h> +#include <linux/delay.h> +#include <linux/fpga/fpga-mgr.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/spi/spi.h> + +#define MPF_SPI_ISC_ENABLE 0x0B +#define MPF_SPI_ISC_DISABLE 0x0C +#define MPF_SPI_READ_STATUS 0x00 +#define MPF_SPI_READ_DATA 0x01 +#define MPF_SPI_FRAME_INIT 0xAE +#define MPF_SPI_FRAME 0xEE +#define MPF_SPI_PRG_MODE 0x01 +#define MPF_SPI_RELEASE 0x23 + +#define MPF_SPI_FRAME_SIZE 16 + +#define MPF_HEADER_SIZE_OFFSET 24 +#define MPF_DATA_SIZE_OFFSET 55 + +#define MPF_LOOKUP_TABLE_RECORD_SIZE 9 +#define MPF_LOOKUP_TABLE_BLOCK_ID_OFFSET 0 +#define MPF_LOOKUP_TABLE_BLOCK_START_OFFSET 1 + +#define MPF_COMPONENTS_SIZE_ID 5 +#define MPF_BITSTREAM_ID 8 + +#define MPF_BITS_PER_COMPONENT_SIZE 22 + +#define MPF_STATUS_POLL_RETRIES 10000 +#define MPF_STATUS_BUSY BIT(0) +#define MPF_STATUS_READY BIT(1) +#define MPF_STATUS_SPI_VIOLATION BIT(2) +#define MPF_STATUS_SPI_ERROR BIT(3) + +struct mpf_priv { + struct spi_device *spi; + bool program_mode; +}; + +static int mpf_read_status(struct spi_device *spi) +{ + u8 status = 0, status_command = MPF_SPI_READ_STATUS; + struct spi_transfer xfers[2] = { 0 }; + int ret; + + /* + * HW status is returned on MISO in the first byte after CS went + * active. However, first reading can be inadequate, so we submit + * two identical SPI transfers and use result of the later one. + */ + xfers[0].tx_buf = &status_command; + xfers[1].tx_buf = &status_command; + xfers[0].rx_buf = &status; + xfers[1].rx_buf = &status; + xfers[0].len = 1; + xfers[1].len = 1; + xfers[0].cs_change = 1; + + ret = spi_sync_transfer(spi, xfers, 2); + + if ((status & MPF_STATUS_SPI_VIOLATION) || + (status & MPF_STATUS_SPI_ERROR)) + ret = -EIO; + + return ret ? : status; +} + +static enum fpga_mgr_states mpf_ops_state(struct fpga_manager *mgr) +{ + struct mpf_priv *priv = mgr->priv; + struct spi_device *spi; + bool program_mode; + int status; + + spi = priv->spi; + program_mode = priv->program_mode; + status = mpf_read_status(spi); + + if (!program_mode && !status) + return FPGA_MGR_STATE_OPERATING; + + return FPGA_MGR_STATE_UNKNOWN; +} + +static int mpf_ops_parse_header(struct fpga_manager *mgr, + struct fpga_image_info *info, + const char *buf, size_t count) +{ + size_t component_size_byte_num, component_size_byte_off, + components_size_start, bitstream_start, + block_id_offset, block_start_offset; + u8 header_size, blocks_num, block_id; + u32 block_start, component_size; + u16 components_num, i; + + if (!buf) { + dev_err(&mgr->dev, "Image buffer is not provided\n"); + return -EINVAL; + } + + header_size = *(buf + MPF_HEADER_SIZE_OFFSET); + if (header_size > count) { + info->header_size = header_size; + return -EAGAIN; + } + + /* + * Go through look-up table to find out where actual bitstream starts + * and where sizes of components of the bitstream lies. + */ + blocks_num = *(buf + header_size - 1); + block_id_offset = header_size + MPF_LOOKUP_TABLE_BLOCK_ID_OFFSET; + block_start_offset = header_size + MPF_LOOKUP_TABLE_BLOCK_START_OFFSET; + + header_size += blocks_num * MPF_LOOKUP_TABLE_RECORD_SIZE; + if (header_size > count) { + info->header_size = header_size; + return -EAGAIN; + } + + components_size_start = 0; + bitstream_start = 0; + + while (blocks_num--) { + block_id = *(buf + block_id_offset); + block_start = get_unaligned_le32(buf + block_start_offset); + + switch (block_id) { + case MPF_BITSTREAM_ID: + bitstream_start = block_start; + info->header_size = block_start; + if (block_start > count) + return -EAGAIN; + + break; + case MPF_COMPONENTS_SIZE_ID: + components_size_start = block_start; + break; + default: + break; + } + + if (bitstream_start && components_size_start) + break; + + block_id_offset += MPF_LOOKUP_TABLE_RECORD_SIZE; + block_start_offset += MPF_LOOKUP_TABLE_RECORD_SIZE; + } + + if (!bitstream_start || !components_size_start) { + dev_err(&mgr->dev, "Failed to parse header look-up table\n"); + return -EFAULT; + } + + /* + * Parse bitstream size. + * Sizes of components of the bitstream are 22-bits long placed next + * to each other. Image header should be extended by now up to where + * actual bitstream starts, so no need for overflow check anymore. + */ + components_num = get_unaligned_le16(buf + MPF_DATA_SIZE_OFFSET); + + for (i = 0; i < components_num; i++) { + component_size_byte_num = + (i * MPF_BITS_PER_COMPONENT_SIZE) / BITS_PER_BYTE; + component_size_byte_off = + (i * MPF_BITS_PER_COMPONENT_SIZE) % BITS_PER_BYTE; + + component_size = get_unaligned_le32(buf + + components_size_start + + component_size_byte_num); + component_size >>= component_size_byte_off; + component_size &= GENMASK(MPF_BITS_PER_COMPONENT_SIZE - 1, 0); + + info->data_size += component_size * MPF_SPI_FRAME_SIZE; + } + + return 0; +} + +/* Poll HW status until busy bit is cleared and mask bits are set. */ +static int mpf_poll_status(struct spi_device *spi, u8 mask) +{ + int status, retries = MPF_STATUS_POLL_RETRIES; + + while (retries--) { + status = mpf_read_status(spi); + if (status < 0) + return status; + + if (status & MPF_STATUS_BUSY) + continue; + + if (!mask || (status & mask)) + return status; + } + + return -EBUSY; +} + +static int mpf_spi_write(struct spi_device *spi, const void *buf, size_t buf_size) +{ + int status = mpf_poll_status(spi, 0); + + if (status < 0) + return status; + + return spi_write(spi, buf, buf_size); +} + +static int mpf_spi_write_then_read(struct spi_device *spi, + const void *txbuf, size_t txbuf_size, + void *rxbuf, size_t rxbuf_size) +{ + const u8 read_command[] = { MPF_SPI_READ_DATA }; + int ret; + + ret = mpf_spi_write(spi, txbuf, txbuf_size); + if (ret) + return ret; + + ret = mpf_poll_status(spi, MPF_STATUS_READY); + if (ret < 0) + return ret; + + return spi_write_then_read(spi, read_command, sizeof(read_command), + rxbuf, rxbuf_size); +} + +static int mpf_ops_write_init(struct fpga_manager *mgr, + struct fpga_image_info *info, const char *buf, + size_t count) +{ + const u8 program_mode[] = { MPF_SPI_FRAME_INIT, MPF_SPI_PRG_MODE }; + const u8 isc_en_command[] = { MPF_SPI_ISC_ENABLE }; + struct mpf_priv *priv = mgr->priv; + struct device *dev = &mgr->dev; + struct spi_device *spi; + u32 isc_ret = 0; + int ret; + + if (info->flags & FPGA_MGR_PARTIAL_RECONFIG) { + dev_err(dev, "Partial reconfiguration is not supported\n"); + return -EOPNOTSUPP; + } + + spi = priv->spi; + + ret = mpf_spi_write_then_read(spi, isc_en_command, sizeof(isc_en_command), + &isc_ret, sizeof(isc_ret)); + if (ret || isc_ret) { + dev_err(dev, "Failed to enable ISC: spi_ret %d, isc_ret %u\n", + ret, isc_ret); + return -EFAULT; + } + + ret = mpf_spi_write(spi, program_mode, sizeof(program_mode)); + if (ret) { + dev_err(dev, "Failed to enter program mode: %d\n", ret); + return ret; + } + + priv->program_mode = true; + + return 0; +} + +static int mpf_ops_write(struct fpga_manager *mgr, const char *buf, size_t count) +{ + u8 spi_frame_command[] = { MPF_SPI_FRAME }; + struct spi_transfer xfers[2] = { 0 }; + struct mpf_priv *priv = mgr->priv; + struct device *dev = &mgr->dev; + struct spi_device *spi; + int ret, i; + + if (count % MPF_SPI_FRAME_SIZE) { + dev_err(dev, "Bitstream size is not a multiple of %d\n", + MPF_SPI_FRAME_SIZE); + return -EINVAL; + } + + spi = priv->spi; + + xfers[0].tx_buf = spi_frame_command; + xfers[0].len = sizeof(spi_frame_command); + + for (i = 0; i < count / MPF_SPI_FRAME_SIZE; i++) { + xfers[1].tx_buf = buf + i * MPF_SPI_FRAME_SIZE; + xfers[1].len = MPF_SPI_FRAME_SIZE; + + ret = mpf_poll_status(spi, 0); + if (ret >= 0) + ret = spi_sync_transfer(spi, xfers, ARRAY_SIZE(xfers)); + + if (ret) { + dev_err(dev, "Failed to write bitstream frame %d/%zu\n", + i, count / MPF_SPI_FRAME_SIZE); + return ret; + } + } + + return 0; +} + +static int mpf_ops_write_complete(struct fpga_manager *mgr, + struct fpga_image_info *info) +{ + const u8 isc_dis_command[] = { MPF_SPI_ISC_DISABLE }; + const u8 release_command[] = { MPF_SPI_RELEASE }; + struct mpf_priv *priv = mgr->priv; + struct device *dev = &mgr->dev; + struct spi_device *spi; + int ret; + + spi = priv->spi; + + ret = mpf_spi_write(spi, isc_dis_command, sizeof(isc_dis_command)); + if (ret) { + dev_err(dev, "Failed to disable ISC: %d\n", ret); + return ret; + } + + usleep_range(1000, 2000); + + ret = mpf_spi_write(spi, release_command, sizeof(release_command)); + if (ret) { + dev_err(dev, "Failed to exit program mode: %d\n", ret); + return ret; + } + + priv->program_mode = false; + + return 0; +} + +static const struct fpga_manager_ops mpf_ops = { + .state = mpf_ops_state, + .initial_header_size = 71, + .skip_header = true, + .parse_header = mpf_ops_parse_header, + .write_init = mpf_ops_write_init, + .write = mpf_ops_write, + .write_complete = mpf_ops_write_complete, +}; + +static int mpf_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct fpga_manager *mgr; + struct mpf_priv *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->spi = spi; + + mgr = devm_fpga_mgr_register(dev, "Microchip Polarfire SPI FPGA Manager", + &mpf_ops, priv); + + return PTR_ERR_OR_ZERO(mgr); +} + +static const struct spi_device_id mpf_spi_ids[] = { + { .name = "mpf-spi-fpga-mgr", }, + {}, +}; +MODULE_DEVICE_TABLE(spi, mpf_spi_ids); + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id mpf_of_ids[] = { + { .compatible = "microchip,mpf-spi-fpga-mgr" }, + {}, +}; +MODULE_DEVICE_TABLE(of, mpf_of_ids); +#endif /* IS_ENABLED(CONFIG_OF) */ + +static struct spi_driver mpf_driver = { + .probe = mpf_probe, + .id_table = mpf_spi_ids, + .driver = { + .name = "microchip_mpf_spi_fpga_mgr", + .of_match_table = of_match_ptr(mpf_of_ids), + }, +}; + +module_spi_driver(mpf_driver); + +MODULE_DESCRIPTION("Microchip Polarfire SPI FPGA Manager"); +MODULE_LICENSE("GPL"); |