diff options
-rw-r--r-- | board/keymile/kmcent2/kmcent2.env | 2 | ||||
-rw-r--r-- | boot/Kconfig | 9 | ||||
-rw-r--r-- | boot/image-fdt.c | 20 | ||||
-rw-r--r-- | configs/verdin-am62_a53_defconfig | 2 | ||||
-rw-r--r-- | drivers/iommu/Kconfig | 16 | ||||
-rw-r--r-- | drivers/iommu/Makefile | 1 | ||||
-rw-r--r-- | drivers/iommu/iommu-uclass.c | 16 | ||||
-rw-r--r-- | drivers/iommu/qcom-hyp-smmu.c | 396 | ||||
-rw-r--r-- | fs/fs.c | 2 | ||||
-rw-r--r-- | include/env/ti/ti_common.env | 5 | ||||
-rw-r--r-- | include/iommu.h | 9 | ||||
-rw-r--r-- | lib/rsa/rsa-sign.c | 3 |
12 files changed, 471 insertions, 10 deletions
diff --git a/board/keymile/kmcent2/kmcent2.env b/board/keymile/kmcent2/kmcent2.env index efa762e5589..dc5508e315c 100644 --- a/board/keymile/kmcent2/kmcent2.env +++ b/board/keymile/kmcent2/kmcent2.env @@ -21,7 +21,7 @@ update=protect off CONFIG_SYS_MONITOR_BASE +${filesize} && erase CONFIG_SYS_MONITOR_BASE +${filesize} && cp.b ${load_addr_r} CONFIG_SYS_MONITOR_BASE ${filesize} && protect on CONFIG_SYS_MONITOR_BASE +${filesize} - update-nor=protect off CONFIG_SYS_FLASH_BASE +${filesize} && +update-nor=protect off CONFIG_SYS_FLASH_BASE +${filesize} && erase CONFIG_SYS_FLASH_BASE +${filesize} && cp.b ${load_addr_r} CONFIG_SYS_FLASH_BASE ${filesize} && protect on CONFIG_SYS_MONITOR_BASE +CONFIG_SYS_MONITOR_LEN diff --git a/boot/Kconfig b/boot/Kconfig index 987ca731411..9f5b8a0cb2c 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -1514,6 +1514,15 @@ if OF_LIBFDT menu "Devicetree fixup" +config OF_ENV_SETUP + bool "Run a command from environment to set up device tree before boot" + depends on CMD_FDT + help + This causes U-Boot to run a command from the environment variable + fdt_fixup before booting into the operating system, which can use the + fdt command to modify the device tree. The device tree is then passed + to the OS. + config OF_BOARD_SETUP bool "Set up board-specific details in device tree before boot" help diff --git a/boot/image-fdt.c b/boot/image-fdt.c index 2b166c0ff94..75bdd55f326 100644 --- a/boot/image-fdt.c +++ b/boot/image-fdt.c @@ -9,6 +9,7 @@ */ #include <common.h> +#include <command.h> #include <fdt_support.h> #include <fdtdec.h> #include <env.h> @@ -576,9 +577,22 @@ int image_setup_libfdt(struct bootm_headers *images, void *blob, { ulong *initrd_start = &images->initrd_start; ulong *initrd_end = &images->initrd_end; - int ret = -EPERM; - int fdt_ret; - int of_size; + int ret, fdt_ret, of_size; + + if (IS_ENABLED(CONFIG_OF_ENV_SETUP)) { + const char *fdt_fixup; + + fdt_fixup = env_get("fdt_fixup"); + if (fdt_fixup) { + set_working_fdt_addr(map_to_sysmem(blob)); + ret = run_command_list(fdt_fixup, -1, 0); + if (ret) + printf("WARNING: fdt_fixup command returned %d\n", + ret); + } + } + + ret = -EPERM; if (fdt_root(blob) < 0) { printf("ERROR: root node setup failed\n"); diff --git a/configs/verdin-am62_a53_defconfig b/configs/verdin-am62_a53_defconfig index 78086018087..cdd6ba8baed 100644 --- a/configs/verdin-am62_a53_defconfig +++ b/configs/verdin-am62_a53_defconfig @@ -17,6 +17,7 @@ CONFIG_ENV_OFFSET=0xFFFFDE00 CONFIG_DM_GPIO=y CONFIG_DEFAULT_DEVICE_TREE="k3-am625-verdin-wifi-dev" CONFIG_SPL_TEXT_BASE=0x80080000 +CONFIG_OF_LIBFDT_OVERLAY=y CONFIG_DM_RESET=y CONFIG_SPL_MMC=y CONFIG_SPL_SERIAL=y @@ -33,7 +34,6 @@ CONFIG_SYS_MEMTEST_END=0xB0000000 CONFIG_FIT_VERBOSE=y CONFIG_SPL_LOAD_FIT=y CONFIG_SPL_LOAD_FIT_ADDRESS=0x81000000 -CONFIG_SPL_LOAD_FIT_APPLY_OVERLAY=y CONFIG_LEGACY_IMAGE_FORMAT=y CONFIG_DISTRO_DEFAULTS=y CONFIG_BOOTDELAY=1 diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig index dabc1f900d5..2ba6d9c1362 100644 --- a/drivers/iommu/Kconfig +++ b/drivers/iommu/Kconfig @@ -24,4 +24,20 @@ config APPLE_DART configuration to put the DART into bypass mode such that it can be used transparently by U-Boot. +config QCOM_HYP_SMMU + bool "Qualcomm quirky SMMU support" + depends on IOMMU && ARCH_SNAPDRAGON + help + Enable support for the Qualcomm variant of the Arm System MMU-500. + Qualcomm boards have a non-standard SMMU where some registers are + emulated by the hypervisor. It is initialised early in the boot + process and can't be turned off. + + The main caveat with this hardware is that it doesn't support BYPASS + streams, attempting to configure once will instead wind up with a + FAULT stream, and the device will crash when DMA is attempted. + + Say Y here to enable support for non-boot peripherals like USB by + configuring identity mapped streams for them. + endmenu diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile index e3e0900e170..438cab8a7c4 100644 --- a/drivers/iommu/Makefile +++ b/drivers/iommu/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_IOMMU) += iommu-uclass.o obj-$(CONFIG_APPLE_DART) += apple_dart.o obj-$(CONFIG_SANDBOX) += sandbox_iommu.o +obj-$(CONFIG_QCOM_HYP_SMMU) += qcom-hyp-smmu.o diff --git a/drivers/iommu/iommu-uclass.c b/drivers/iommu/iommu-uclass.c index 72f123df55a..6babc0e3a67 100644 --- a/drivers/iommu/iommu-uclass.c +++ b/drivers/iommu/iommu-uclass.c @@ -77,6 +77,7 @@ int dev_iommu_enable(struct udevice *dev) { struct ofnode_phandle_args args; struct udevice *dev_iommu; + const struct iommu_ops *ops; int i, count, ret = 0; count = dev_count_phandle_with_args(dev, "iommus", @@ -98,11 +99,22 @@ int dev_iommu_enable(struct udevice *dev) return ret; } dev->iommu = dev_iommu; + + if (dev->parent && dev->parent->iommu == dev_iommu) + continue; + + ops = device_get_ops(dev->iommu); + if (ops && ops->connect) { + ret = ops->connect(dev); + if (ret) + return ret; + } } - if (CONFIG_IS_ENABLED(PCI) && count < 0 && - device_is_on_pci_bus(dev)) +#if CONFIG_IS_ENABLED(PCI) + if (count < 0 && device_is_on_pci_bus(dev)) return dev_pci_iommu_enable(dev); +#endif return 0; } diff --git a/drivers/iommu/qcom-hyp-smmu.c b/drivers/iommu/qcom-hyp-smmu.c new file mode 100644 index 00000000000..8e5cdb58155 --- /dev/null +++ b/drivers/iommu/qcom-hyp-smmu.c @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2023 Linaro Ltd. + * Basic ARM SMMU-500 driver, assuming a pre-initialised SMMU and only IDENTITY domains + * this driver only implements the bare minimum to configure stream mappings for periphals + * used by u-boot on platforms where the SMMU can't be disabled. + */ + +#include <log.h> +#include <cpu_func.h> +#include <dm.h> +#include <iommu.h> +#include <linux/bitfield.h> +#include <linux/list.h> +#include <linux/err.h> +#include <lmb.h> +#include <memalign.h> +#include <asm/io.h> + +#define ARM_SMMU_GR0 0 +#define ARM_SMMU_GR1 1 + +#define ARM_SMMU_GR0_ID0 0x20 +#define ARM_SMMU_ID0_NUMSMRG GENMASK(7, 0) /* Number of stream mapping groups */ +#define ARM_SMMU_GR0_ID1 0x24 +#define ARM_SMMU_ID1_PAGESIZE \ + BIT(31) /* Page shift is 16 bits when set, otherwise 23 */ +#define ARM_SMMU_ID1_NUMPAGENDXB \ + GENMASK(30, 28) /* Number of pages before context banks */ +#define ARM_SMMU_ID1_NUMCB GENMASK(7, 0) /* Number of context banks supported */ + +#define ARM_SMMU_GR1_CBAR(n) (0x0 + ((n) << 2)) +#define ARM_SMMU_CBAR_TYPE GENMASK(17, 16) +#define ARM_SMMU_CBAR_VMID GENMASK(7, 0) +enum arm_smmu_cbar_type { + CBAR_TYPE_S2_TRANS, + CBAR_TYPE_S1_TRANS_S2_BYPASS, + CBAR_TYPE_S1_TRANS_S2_FAULT, + CBAR_TYPE_S1_TRANS_S2_TRANS, +}; + +#define ARM_SMMU_GR1_CBA2R(n) (0x800 + ((n) << 2)) +#define ARM_SMMU_CBA2R_VA64 BIT(0) + +/* Per-CB system control register */ +#define ARM_SMMU_CB_SCTLR 0x0 +#define ARM_SMMU_SCTLR_CFCFG BIT(7) /* Stall on context fault */ +#define ARM_SMMU_SCTLR_CFIE BIT(6) /* Context fault interrupt enable */ +#define ARM_SMMU_SCTLR_CFRE BIT(5) /* Abort on context fault */ + +/* Translation Table Base, holds address of translation table in memory to be used + * for this context bank. Or 0 for bypass + */ +#define ARM_SMMU_CB_TTBR0 0x20 +#define ARM_SMMU_CB_TTBR1 0x28 +/* Translation Control Register, configured TTBR/TLB behaviour (0 for bypass) */ +#define ARM_SMMU_CB_TCR 0x30 +/* Memory Attribute Indirection, also 0 for bypass */ +#define ARM_SMMU_CB_S1_MAIR0 0x38 +#define ARM_SMMU_CB_S1_MAIR1 0x3c + +#define ARM_SMMU_GR0_SMR(n) (0x800 + ((n) << 2)) +#define ARM_SMMU_SMR_VALID BIT(31) +#define ARM_SMMU_SMR_MASK GENMASK(31, 16) // Always 0 for now?? +#define ARM_SMMU_SMR_ID GENMASK(15, 0) + +#define ARM_SMMU_GR0_S2CR(n) (0xc00 + ((n) << 2)) +#define ARM_SMMU_S2CR_PRIVCFG GENMASK(25, 24) + +enum arm_smmu_s2cr_privcfg { + S2CR_PRIVCFG_DEFAULT, + S2CR_PRIVCFG_DIPAN, + S2CR_PRIVCFG_UNPRIV, + S2CR_PRIVCFG_PRIV, +}; + +#define ARM_SMMU_S2CR_TYPE GENMASK(17, 16) + +enum arm_smmu_s2cr_type { + S2CR_TYPE_TRANS, + S2CR_TYPE_BYPASS, + S2CR_TYPE_FAULT, +}; + +#define ARM_SMMU_S2CR_EXIDVALID BIT(10) +#define ARM_SMMU_S2CR_CBNDX GENMASK(7, 0) + +#define VMID_UNUSED 0xff + +struct qcom_smmu_priv { + phys_addr_t base; + struct list_head devices; + struct udevice *dev; + + /* Read-once config */ + int num_cb; + int num_smr; + u32 pgshift; + u32 cb_pg_offset; +}; + +struct mmu_dev { + struct list_head li; + struct udevice *dev; + u16 sid; + u16 cbx; + u16 smr; +}; + +#define page_addr(priv, page) ((priv)->base + ((page) << (priv)->pgshift)) + +#define smmu_readl(priv, page, offset) readl(page_addr(priv, page) + offset) +#define gr0_readl(priv, offset) smmu_readl(priv, ARM_SMMU_GR0, offset) +#define gr1_readl(priv, offset) smmu_readl(priv, ARM_SMMU_GR1, offset) +#define cbx_readl(priv, cbx, offset) \ + smmu_readl(priv, (priv->cb_pg_offset) + cbx, offset) + +#define smmu_writel(priv, page, offset, value) \ + writel((value), page_addr(priv, page) + offset) +#define gr0_writel(priv, offset, value) \ + smmu_writel(priv, ARM_SMMU_GR0, offset, (value)) +#define gr1_writel(priv, offset, value) \ + smmu_writel(priv, ARM_SMMU_GR1, offset, (value)) +#define cbx_writel(priv, cbx, offset, value) \ + smmu_writel(priv, (priv->cb_pg_offset) + cbx, offset, value) + +#define gr1_setbits(priv, offset, value) \ + gr1_writel(priv, offset, gr1_readl(priv, offset) | (value)) + +static int get_stream_id(struct udevice *dev) +{ + ofnode node = dev_ofnode(dev); + struct ofnode_phandle_args args; + int count = ofnode_parse_phandle_with_args(node, "iommus", + "#iommu-cells", 0, 0, &args); + + if (count < 0 || args.args[0] == 0) { + printf("Error: %s: iommus property not found or wrong number of cells\n", + __func__); + return -EINVAL; + } + + return args.args[0]; // Some mask from bit 16 onward? +} + +static struct mmu_dev *alloc_dev(struct udevice *dev) +{ + struct qcom_smmu_priv *priv = dev_get_priv(dev->iommu); + struct mmu_dev *mmu_dev; + int sid; + + sid = get_stream_id(dev); + debug("%s %s has SID %#x\n", dev->iommu->name, dev->name, sid); + if (sid < 0 || sid > 0xffff) { + printf("\tSMMU: Invalid stream ID for %s\n", dev->name); + return ERR_PTR(-EINVAL); + } + + /* We only support a single SID per device for now */ + list_for_each_entry(mmu_dev, &priv->devices, li) { + if (mmu_dev->sid == sid) + return ERR_PTR(-EEXIST); + } + + mmu_dev = calloc(sizeof(*mmu_dev), 1); + if (!mmu_dev) + return ERR_PTR(-ENOMEM); + + mmu_dev->dev = dev; + mmu_dev->sid = sid; + + list_add_tail(&mmu_dev->li, &priv->devices); + + return mmu_dev; +} + +/* Find and init the first free context bank */ +static int alloc_cb(struct qcom_smmu_priv *priv) +{ + u32 cbar, type, vmid, val; + + for (int i = 0; i < priv->num_cb; i++) { + cbar = gr1_readl(priv, ARM_SMMU_GR1_CBAR(i)); + type = FIELD_GET(ARM_SMMU_CBAR_TYPE, cbar); + vmid = FIELD_GET(ARM_SMMU_CBAR_VMID, cbar); + + /* Check that the context bank is available. We haven't reset the SMMU so + * we just make a best guess. + */ + if (type != CBAR_TYPE_S2_TRANS && + (type != CBAR_TYPE_S1_TRANS_S2_BYPASS || + vmid != VMID_UNUSED)) + continue; + + debug("%s: Found free context bank %d (cbar %#x)\n", + priv->dev->name, i, cbar); + type = CBAR_TYPE_S1_TRANS_S2_BYPASS; + vmid = 0; + cbar &= ~ARM_SMMU_CBAR_TYPE & ~ARM_SMMU_CBAR_VMID; + cbar |= FIELD_PREP(ARM_SMMU_CBAR_TYPE, type) | + FIELD_PREP(ARM_SMMU_CBAR_VMID, vmid); + gr1_writel(priv, ARM_SMMU_GR1_CBAR(i), cbar); + + val = IS_ENABLED(CONFIG_ARM64) == 1 ? ARM_SMMU_CBA2R_VA64 : 0; + gr1_setbits(priv, ARM_SMMU_GR1_CBA2R(i), val); + return i; + } + + return -1; +} + +/* Search for a context bank that is already configured for this stream + * returns the context bank index or -ENOENT + */ +static int find_smr(struct qcom_smmu_priv *priv, u16 stream_id) +{ + u32 val; + int i; + + for (i = 0; i < priv->num_smr; i++) { + val = gr0_readl(priv, ARM_SMMU_GR0_SMR(i)); + if (!(val & ARM_SMMU_SMR_VALID) || + FIELD_GET(ARM_SMMU_SMR_ID, val) != stream_id) + continue; + + return i; + } + + return -ENOENT; +} + +static int configure_smr_s2cr(struct qcom_smmu_priv *priv, struct mmu_dev *mdev) +{ + u32 val; + int i; + + for (i = 0; i < priv->num_smr; i++) { + /* Configure SMR */ + val = gr0_readl(priv, ARM_SMMU_GR0_SMR(i)); + if (val & ARM_SMMU_SMR_VALID) + continue; + + val = mdev->sid | ARM_SMMU_SMR_VALID; + gr0_writel(priv, ARM_SMMU_GR0_SMR(i), val); + + /* + * WARNING: Don't change this to use S2CR_TYPE_BYPASS! + * Some Qualcomm boards have angry hypervisor firmware + * that converts S2CR type BYPASS to type FAULT on write. + * We don't use virtual addressing for these boards in + * u-boot so we can get away with using S2CR_TYPE_TRANS + * instead + */ + val = FIELD_PREP(ARM_SMMU_S2CR_TYPE, S2CR_TYPE_TRANS) | + FIELD_PREP(ARM_SMMU_S2CR_CBNDX, mdev->cbx); + gr0_writel(priv, ARM_SMMU_GR0_S2CR(i), val); + + mdev->smr = i; + break; + } + + /* Make sure our writes went through */ + mb(); + + return 0; +} + +static int qcom_smmu_connect(struct udevice *dev) +{ + struct mmu_dev *mdev; + struct qcom_smmu_priv *priv; + int ret; + + debug("%s: %s -> %s\n", __func__, dev->name, dev->iommu->name); + + priv = dev_get_priv(dev->iommu); + if (WARN_ON(!priv)) + return -EINVAL; + + mdev = alloc_dev(dev); + if (IS_ERR(mdev) && PTR_ERR(mdev) != -EEXIST) { + printf("%s: %s Couldn't create mmu context\n", __func__, + dev->name); + return PTR_ERR(mdev); + } else if (IS_ERR(mdev)) { // -EEXIST + return 0; + } + + if (find_smr(priv, mdev->sid) >= 0) { + debug("Found existing context bank for %s, skipping init\n", + dev->name); + return 0; + } + + ret = alloc_cb(priv); + if (ret < 0 || ret > 0xff) { + printf("Error: %s: failed to allocate context bank for %s\n", + __func__, dev->name); + return 0; + } + mdev->cbx = ret; + + /* Configure context bank registers */ + cbx_writel(priv, mdev->cbx, ARM_SMMU_CB_TTBR0, 0x0); + cbx_writel(priv, mdev->cbx, ARM_SMMU_CB_TTBR1, 0x0); + cbx_writel(priv, mdev->cbx, ARM_SMMU_CB_S1_MAIR0, 0x0); + cbx_writel(priv, mdev->cbx, ARM_SMMU_CB_S1_MAIR1, 0x0); + cbx_writel(priv, mdev->cbx, ARM_SMMU_CB_SCTLR, + ARM_SMMU_SCTLR_CFIE | ARM_SMMU_SCTLR_CFRE | + ARM_SMMU_SCTLR_CFCFG); + cbx_writel(priv, mdev->cbx, ARM_SMMU_CB_TCR, 0x0); + + /* Ensure that our writes went through */ + mb(); + + configure_smr_s2cr(priv, mdev); + + return 0; +} + +#ifdef DEBUG +static inline void dump_boot_mappings(struct arm_smmu_priv *priv) +{ + u32 val; + int i; + + debug(" SMMU dump boot mappings:\n"); + for (i = 0; i < priv->num_smr; i++) { + val = gr0_readl(priv, ARM_SMMU_GR0_SMR(i)); + if (val & ARM_SMMU_SMR_VALID) + debug("\tSMR %3d: SID: %#lx\n", i, + FIELD_GET(ARM_SMMU_SMR_ID, val)); + } +} +#else +#define dump_boot_mappings(priv) \ + do { \ + } while (0) +#endif + +static int qcom_smmu_probe(struct udevice *dev) +{ + struct qcom_smmu_priv *priv; + u32 val; + + priv = dev_get_priv(dev); + priv->dev = dev; + priv->base = dev_read_addr(dev); + INIT_LIST_HEAD(&priv->devices); + + /* Read SMMU config */ + val = gr0_readl(priv, ARM_SMMU_GR0_ID0); + priv->num_smr = FIELD_GET(ARM_SMMU_ID0_NUMSMRG, val); + + val = gr0_readl(priv, ARM_SMMU_GR0_ID1); + priv->num_cb = FIELD_GET(ARM_SMMU_ID1_NUMCB, val); + priv->pgshift = FIELD_GET(ARM_SMMU_ID1_PAGESIZE, val) ? 16 : 12; + priv->cb_pg_offset = 1 + << (FIELD_GET(ARM_SMMU_ID1_NUMPAGENDXB, val) + 1); + + dump_boot_mappings(priv); + + return 0; +} + +static int qcom_smmu_remove(struct udevice *dev) +{ + (void)dev; + /* + * We should probably try and de-configure things here, + * however I'm yet to find a way to do it without crashing + * and it seems like Linux doesn't care at all anyway. + */ + + return 0; +} + +static struct iommu_ops qcom_smmu_ops = { + .connect = qcom_smmu_connect, +}; + +static const struct udevice_id qcom_smmu500_ids[] = { + { .compatible = "qcom,sdm845-smmu-500" }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(qcom_smmu500) = { + .name = "qcom_smmu500", + .id = UCLASS_IOMMU, + .of_match = qcom_smmu500_ids, + .priv_auto = sizeof(struct qcom_smmu_priv), + .ops = &qcom_smmu_ops, + .probe = qcom_smmu_probe, + .remove = qcom_smmu_remove, + .flags = DM_FLAG_OS_PREPARE, +}; @@ -256,7 +256,7 @@ static struct fstype_info fstypes[] = { .ln = fs_ln_unsupported, }, #endif -#ifdef CONFIG_SEMIHOSTING +#if CONFIG_IS_ENABLED(SEMIHOSTING) { .fstype = FS_TYPE_SEMIHOSTING, .name = "semihosting", diff --git a/include/env/ti/ti_common.env b/include/env/ti/ti_common.env index f5d84216e3c..f0f89a22876 100644 --- a/include/env/ti/ti_common.env +++ b/include/env/ti/ti_common.env @@ -25,7 +25,10 @@ run_fit=run get_fit_config; bootm ${addr_fit}#${name_fit_config}${overlaystring} bootcmd_ti_mmc= run findfdt; run init_${boot}; #if CONFIG_CMD_REMOTEPROC - run main_cpsw0_qsgmii_phyinit; run boot_rprocs; + if test ${do_main_cpsw0_qsgmii_phyinit} -eq 1; + then run main_cpsw0_qsgmii_phyinit; + fi + run boot_rprocs; #endif if test ${boot_fit} -eq 1; then run get_fit_${boot}; run get_fit_overlaystring; run run_fit; diff --git a/include/iommu.h b/include/iommu.h index cf9719c5e91..b8ba0b8e707 100644 --- a/include/iommu.h +++ b/include/iommu.h @@ -5,6 +5,15 @@ struct udevice; struct iommu_ops { /** + * init() - Connect a device to it's IOMMU, called before probe() + * The iommu device can be fetched through dev->iommu + * + * @iommu_dev: IOMMU device + * @dev: Device to connect + * @return 0 if OK, -errno on error + */ + int (*connect)(struct udevice *dev); + /** * map() - map DMA memory * * @dev: device for which to map DMA memory diff --git a/lib/rsa/rsa-sign.c b/lib/rsa/rsa-sign.c index 7ae163f264b..858ad92a6f6 100644 --- a/lib/rsa/rsa-sign.c +++ b/lib/rsa/rsa-sign.c @@ -317,7 +317,8 @@ static int rsa_engine_init(const char *engine_id, ENGINE **pe) e = ENGINE_by_id(engine_id); if (!e) { - fprintf(stderr, "Engine isn't available\n"); + fprintf(stderr, "Engine '%s' isn't available\n", engine_id); + ERR_print_errors_fp(stderr); return -1; } |