diff options
Diffstat (limited to 'drivers/firmware')
| -rw-r--r-- | drivers/firmware/cirrus/cs_dsp.c | 175 | ||||
| -rw-r--r-- | drivers/firmware/cirrus/test/cs_dsp_test_callbacks.c | 1 | ||||
| -rw-r--r-- | drivers/firmware/efi/arm-runtime.c | 4 | ||||
| -rw-r--r-- | drivers/firmware/efi/cper-arm.c | 52 | ||||
| -rw-r--r-- | drivers/firmware/efi/cper.c | 62 | ||||
| -rw-r--r-- | drivers/firmware/efi/libstub/efi-stub.c | 2 | ||||
| -rw-r--r-- | drivers/firmware/efi/libstub/efistub.h | 31 | ||||
| -rw-r--r-- | drivers/firmware/efi/libstub/gop.c | 137 | ||||
| -rw-r--r-- | drivers/firmware/efi/libstub/x86-stub.c | 104 | ||||
| -rw-r--r-- | drivers/firmware/efi/memattr.c | 7 | ||||
| -rw-r--r-- | drivers/firmware/efi/riscv-runtime.c | 14 | ||||
| -rw-r--r-- | drivers/firmware/efi/stmm/mm_communication.h | 6 | ||||
| -rw-r--r-- | drivers/firmware/imx/imx-scu-irq.c | 32 | ||||
| -rw-r--r-- | drivers/firmware/imx/imx-scu.c | 11 | ||||
| -rw-r--r-- | drivers/firmware/qcom/qcom_scm.c | 17 | ||||
| -rw-r--r-- | drivers/firmware/ti_sci.c | 155 | ||||
| -rw-r--r-- | drivers/firmware/ti_sci.h | 7 | ||||
| -rw-r--r-- | drivers/firmware/xilinx/Makefile | 2 | ||||
| -rw-r--r-- | drivers/firmware/xilinx/zynqmp-debug.c | 13 | ||||
| -rw-r--r-- | drivers/firmware/xilinx/zynqmp-ufs.c | 118 | ||||
| -rw-r--r-- | drivers/firmware/xilinx/zynqmp.c | 160 |
21 files changed, 810 insertions, 300 deletions
diff --git a/drivers/firmware/cirrus/cs_dsp.c b/drivers/firmware/cirrus/cs_dsp.c index f51047d8ea64..525ac0f0a75d 100644 --- a/drivers/firmware/cirrus/cs_dsp.c +++ b/drivers/firmware/cirrus/cs_dsp.c @@ -9,9 +9,11 @@ * Cirrus Logic International Semiconductor Ltd. */ +#include <linux/cleanup.h> #include <linux/ctype.h> #include <linux/debugfs.h> #include <linux/delay.h> +#include <linux/math.h> #include <linux/minmax.h> #include <linux/module.h> #include <linux/moduleparam.h> @@ -316,44 +318,6 @@ struct cs_dsp_alg_region_list_item { struct cs_dsp_alg_region alg_region; }; -struct cs_dsp_buf { - struct list_head list; - void *buf; -}; - -static struct cs_dsp_buf *cs_dsp_buf_alloc(const void *src, size_t len, - struct list_head *list) -{ - struct cs_dsp_buf *buf = kzalloc(sizeof(*buf), GFP_KERNEL); - - if (buf == NULL) - return NULL; - - buf->buf = vmalloc(len); - if (!buf->buf) { - kfree(buf); - return NULL; - } - memcpy(buf->buf, src, len); - - if (list) - list_add_tail(&buf->list, list); - - return buf; -} - -static void cs_dsp_buf_free(struct list_head *list) -{ - while (!list_empty(list)) { - struct cs_dsp_buf *buf = list_first_entry(list, - struct cs_dsp_buf, - list); - list_del(&buf->list); - vfree(buf->buf); - kfree(buf); - } -} - /** * cs_dsp_mem_region_name() - Return a name string for a memory type * @type: the memory type to match @@ -388,18 +352,14 @@ EXPORT_SYMBOL_NS_GPL(cs_dsp_mem_region_name, "FW_CS_DSP"); #ifdef CONFIG_DEBUG_FS static void cs_dsp_debugfs_save_wmfwname(struct cs_dsp *dsp, const char *s) { - char *tmp = kasprintf(GFP_KERNEL, "%s\n", s); - kfree(dsp->wmfw_file_name); - dsp->wmfw_file_name = tmp; + dsp->wmfw_file_name = kstrdup(s, GFP_KERNEL); } static void cs_dsp_debugfs_save_binname(struct cs_dsp *dsp, const char *s) { - char *tmp = kasprintf(GFP_KERNEL, "%s\n", s); - kfree(dsp->bin_file_name); - dsp->bin_file_name = tmp; + dsp->bin_file_name = kstrdup(s, GFP_KERNEL); } static void cs_dsp_debugfs_clear(struct cs_dsp *dsp) @@ -410,24 +370,33 @@ static void cs_dsp_debugfs_clear(struct cs_dsp *dsp) dsp->bin_file_name = NULL; } +static ssize_t cs_dsp_debugfs_string_read(struct cs_dsp *dsp, + char __user *user_buf, + size_t count, loff_t *ppos, + const char **pstr) +{ + const char *str __free(kfree) = NULL; + + scoped_guard(mutex, &dsp->pwr_lock) { + if (!*pstr) + return 0; + + str = kasprintf(GFP_KERNEL, "%s\n", *pstr); + if (!str) + return -ENOMEM; + + return simple_read_from_buffer(user_buf, count, ppos, str, strlen(str)); + } +} + static ssize_t cs_dsp_debugfs_wmfw_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { struct cs_dsp *dsp = file->private_data; - ssize_t ret; - - mutex_lock(&dsp->pwr_lock); - if (!dsp->wmfw_file_name || !dsp->booted) - ret = 0; - else - ret = simple_read_from_buffer(user_buf, count, ppos, - dsp->wmfw_file_name, - strlen(dsp->wmfw_file_name)); - - mutex_unlock(&dsp->pwr_lock); - return ret; + return cs_dsp_debugfs_string_read(dsp, user_buf, count, ppos, + &dsp->wmfw_file_name); } static ssize_t cs_dsp_debugfs_bin_read(struct file *file, @@ -435,19 +404,9 @@ static ssize_t cs_dsp_debugfs_bin_read(struct file *file, size_t count, loff_t *ppos) { struct cs_dsp *dsp = file->private_data; - ssize_t ret; - - mutex_lock(&dsp->pwr_lock); - if (!dsp->bin_file_name || !dsp->booted) - ret = 0; - else - ret = simple_read_from_buffer(user_buf, count, ppos, - dsp->bin_file_name, - strlen(dsp->bin_file_name)); - - mutex_unlock(&dsp->pwr_lock); - return ret; + return cs_dsp_debugfs_string_read(dsp, user_buf, count, ppos, + &dsp->bin_file_name); } static const struct { @@ -479,9 +438,11 @@ static int cs_dsp_debugfs_read_controls_show(struct seq_file *s, void *ignored) struct cs_dsp_coeff_ctl *ctl; unsigned int reg; + guard(mutex)(&dsp->pwr_lock); + list_for_each_entry(ctl, &dsp->ctl_list, list) { cs_dsp_coeff_base_reg(ctl, ®, 0); - seq_printf(s, "%22.*s: %#8zx %s:%08x %#8x %s %#8x %#4x %c%c%c%c %s %s\n", + seq_printf(s, "%22.*s: %#8x %s:%08x %#8x %s %#8x %#4x %c%c%c%c %s %s\n", ctl->subname_len, ctl->subname, ctl->len, cs_dsp_mem_region_name(ctl->alg_region.type), ctl->offset, reg, ctl->fw_name, ctl->alg_region.alg, ctl->type, @@ -1028,7 +989,7 @@ static void cs_dsp_signal_event_controls(struct cs_dsp *dsp, static void cs_dsp_free_ctl_blk(struct cs_dsp_coeff_ctl *ctl) { - kfree(ctl->cache); + kvfree(ctl->cache); kfree(ctl->subname); kfree(ctl); } @@ -1078,7 +1039,7 @@ static int cs_dsp_create_control(struct cs_dsp *dsp, ctl->type = type; ctl->offset = offset; ctl->len = len; - ctl->cache = kzalloc(ctl->len, GFP_KERNEL); + ctl->cache = kvzalloc(ctl->len, GFP_KERNEL); if (!ctl->cache) { ret = -ENOMEM; goto err_ctl_subname; @@ -1096,7 +1057,7 @@ static int cs_dsp_create_control(struct cs_dsp *dsp, err_list_del: list_del(&ctl->list); - kfree(ctl->cache); + kvfree(ctl->cache); err_ctl_subname: kfree(ctl->subname); err_ctl: @@ -1485,7 +1446,9 @@ static int cs_dsp_load(struct cs_dsp *dsp, const struct firmware *firmware, const struct wmfw_region *region; const struct cs_dsp_region *mem; const char *region_name; - struct cs_dsp_buf *buf; + u8 *buf __free(kfree) = NULL; + size_t buf_len = 0; + size_t region_len; unsigned int reg; int regions = 0; int ret, offset, type; @@ -1605,23 +1568,23 @@ static int cs_dsp_load(struct cs_dsp *dsp, const struct firmware *firmware, region_name); if (reg) { - buf = cs_dsp_buf_alloc(region->data, - le32_to_cpu(region->len), - &buf_list); - if (!buf) { - cs_dsp_err(dsp, "Out of memory\n"); - ret = -ENOMEM; - goto out_fw; + region_len = le32_to_cpu(region->len); + if (region_len > buf_len) { + buf_len = round_up(region_len, PAGE_SIZE); + kfree(buf); + buf = kmalloc(buf_len, GFP_KERNEL | GFP_DMA); + if (!buf) { + ret = -ENOMEM; + goto out_fw; + } } - ret = regmap_raw_write(regmap, reg, buf->buf, - le32_to_cpu(region->len)); + memcpy(buf, region->data, region_len); + ret = regmap_raw_write(regmap, reg, buf, region_len); if (ret != 0) { cs_dsp_err(dsp, - "%s.%d: Failed to write %d bytes at %d in %s: %d\n", - file, regions, - le32_to_cpu(region->len), offset, - region_name, ret); + "%s.%d: Failed to write %zu bytes at %d in %s: %d\n", + file, regions, region_len, offset, region_name, ret); goto out_fw; } } @@ -1638,8 +1601,6 @@ static int cs_dsp_load(struct cs_dsp *dsp, const struct firmware *firmware, ret = 0; out_fw: - cs_dsp_buf_free(&buf_list); - if (ret == -EOVERFLOW) cs_dsp_err(dsp, "%s: file content overflows file data\n", file); @@ -2171,7 +2132,9 @@ static int cs_dsp_load_coeff(struct cs_dsp *dsp, const struct firmware *firmware struct cs_dsp_alg_region *alg_region; const char *region_name; int ret, pos, blocks, type, offset, reg, version; - struct cs_dsp_buf *buf; + u8 *buf __free(kfree) = NULL; + size_t buf_len = 0; + size_t region_len; if (!firmware) return 0; @@ -2313,20 +2276,22 @@ static int cs_dsp_load_coeff(struct cs_dsp *dsp, const struct firmware *firmware } if (reg) { - buf = cs_dsp_buf_alloc(blk->data, - le32_to_cpu(blk->len), - &buf_list); - if (!buf) { - cs_dsp_err(dsp, "Out of memory\n"); - ret = -ENOMEM; - goto out_fw; + region_len = le32_to_cpu(blk->len); + if (region_len > buf_len) { + buf_len = round_up(region_len, PAGE_SIZE); + kfree(buf); + buf = kmalloc(buf_len, GFP_KERNEL | GFP_DMA); + if (!buf) { + ret = -ENOMEM; + goto out_fw; + } } - cs_dsp_dbg(dsp, "%s.%d: Writing %d bytes at %x\n", - file, blocks, le32_to_cpu(blk->len), - reg); - ret = regmap_raw_write(regmap, reg, buf->buf, - le32_to_cpu(blk->len)); + memcpy(buf, blk->data, region_len); + + cs_dsp_dbg(dsp, "%s.%d: Writing %zu bytes at %x\n", + file, blocks, region_len, reg); + ret = regmap_raw_write(regmap, reg, buf, region_len); if (ret != 0) { cs_dsp_err(dsp, "%s.%d: Failed to write to %x in %s: %d\n", @@ -2346,8 +2311,6 @@ static int cs_dsp_load_coeff(struct cs_dsp *dsp, const struct firmware *firmware ret = 0; out_fw: - cs_dsp_buf_free(&buf_list); - if (ret == -EOVERFLOW) cs_dsp_err(dsp, "%s: file content overflows file data\n", file); @@ -2366,6 +2329,9 @@ static int cs_dsp_create_name(struct cs_dsp *dsp) return 0; } +static const struct cs_dsp_client_ops cs_dsp_default_client_ops = { +}; + static int cs_dsp_common_init(struct cs_dsp *dsp) { int ret; @@ -2379,6 +2345,9 @@ static int cs_dsp_common_init(struct cs_dsp *dsp) mutex_init(&dsp->pwr_lock); + if (!dsp->client_ops) + dsp->client_ops = &cs_dsp_default_client_ops; + #ifdef CONFIG_DEBUG_FS /* Ensure this is invalid if client never provides a debugfs root */ dsp->debugfs_root = ERR_PTR(-ENODEV); diff --git a/drivers/firmware/cirrus/test/cs_dsp_test_callbacks.c b/drivers/firmware/cirrus/test/cs_dsp_test_callbacks.c index 8a9b66a3b7d3..e5a389808e5f 100644 --- a/drivers/firmware/cirrus/test/cs_dsp_test_callbacks.c +++ b/drivers/firmware/cirrus/test/cs_dsp_test_callbacks.c @@ -600,6 +600,7 @@ KUNIT_ARRAY_PARAM(cs_dsp_callbacks_ops, static const struct cs_dsp_callbacks_test_param cs_dsp_no_callbacks_cases[] = { { .ops = &cs_dsp_callback_test_empty_client_ops, .case_name = "empty ops" }, + { .ops = NULL, .case_name = "NULL ops" }, }; KUNIT_ARRAY_PARAM(cs_dsp_no_callbacks, diff --git a/drivers/firmware/efi/arm-runtime.c b/drivers/firmware/efi/arm-runtime.c index 83092d93f36a..53a5336cde5a 100644 --- a/drivers/firmware/efi/arm-runtime.c +++ b/drivers/firmware/efi/arm-runtime.c @@ -12,18 +12,18 @@ #include <linux/io.h> #include <linux/memblock.h> #include <linux/mm_types.h> +#include <linux/pgalloc.h> +#include <linux/pgtable.h> #include <linux/preempt.h> #include <linux/rbtree.h> #include <linux/rwsem.h> #include <linux/sched.h> #include <linux/slab.h> #include <linux/spinlock.h> -#include <linux/pgtable.h> #include <asm/cacheflush.h> #include <asm/efi.h> #include <asm/mmu.h> -#include <asm/pgalloc.h> #if defined(CONFIG_PTDUMP_DEBUGFS) || defined(CONFIG_ARM_PTDUMP_DEBUGFS) #include <asm/ptdump.h> diff --git a/drivers/firmware/efi/cper-arm.c b/drivers/firmware/efi/cper-arm.c index f0a63d09d3c4..76542a53e202 100644 --- a/drivers/firmware/efi/cper-arm.c +++ b/drivers/firmware/efi/cper-arm.c @@ -93,15 +93,11 @@ static void cper_print_arm_err_info(const char *pfx, u32 type, bool proc_context_corrupt, corrected, precise_pc, restartable_pc; bool time_out, access_mode; - /* If the type is unknown, bail. */ - if (type > CPER_ARM_MAX_TYPE) - return; - /* * Vendor type errors have error information values that are vendor * specific. */ - if (type == CPER_ARM_VENDOR_ERROR) + if (type & CPER_ARM_VENDOR_ERROR) return; if (error_info & CPER_ARM_ERR_VALID_TRANSACTION_TYPE) { @@ -116,43 +112,38 @@ static void cper_print_arm_err_info(const char *pfx, u32 type, if (error_info & CPER_ARM_ERR_VALID_OPERATION_TYPE) { op_type = ((error_info >> CPER_ARM_ERR_OPERATION_SHIFT) & CPER_ARM_ERR_OPERATION_MASK); - switch (type) { - case CPER_ARM_CACHE_ERROR: + if (type & CPER_ARM_CACHE_ERROR) { if (op_type < ARRAY_SIZE(arm_cache_err_op_strs)) { - printk("%soperation type: %s\n", pfx, + printk("%scache error, operation type: %s\n", pfx, arm_cache_err_op_strs[op_type]); } - break; - case CPER_ARM_TLB_ERROR: + } + if (type & CPER_ARM_TLB_ERROR) { if (op_type < ARRAY_SIZE(arm_tlb_err_op_strs)) { - printk("%soperation type: %s\n", pfx, + printk("%sTLB error, operation type: %s\n", pfx, arm_tlb_err_op_strs[op_type]); } - break; - case CPER_ARM_BUS_ERROR: + } + if (type & CPER_ARM_BUS_ERROR) { if (op_type < ARRAY_SIZE(arm_bus_err_op_strs)) { - printk("%soperation type: %s\n", pfx, + printk("%sbus error, operation type: %s\n", pfx, arm_bus_err_op_strs[op_type]); } - break; } } if (error_info & CPER_ARM_ERR_VALID_LEVEL) { level = ((error_info >> CPER_ARM_ERR_LEVEL_SHIFT) & CPER_ARM_ERR_LEVEL_MASK); - switch (type) { - case CPER_ARM_CACHE_ERROR: + if (type & CPER_ARM_CACHE_ERROR) printk("%scache level: %d\n", pfx, level); - break; - case CPER_ARM_TLB_ERROR: + + if (type & CPER_ARM_TLB_ERROR) printk("%sTLB level: %d\n", pfx, level); - break; - case CPER_ARM_BUS_ERROR: + + if (type & CPER_ARM_BUS_ERROR) printk("%saffinity level at which the bus error occurred: %d\n", pfx, level); - break; - } } if (error_info & CPER_ARM_ERR_VALID_PROC_CONTEXT_CORRUPT) { @@ -240,7 +231,8 @@ void cper_print_proc_arm(const char *pfx, int i, len, max_ctx_type; struct cper_arm_err_info *err_info; struct cper_arm_ctx_info *ctx_info; - char newpfx[64], infopfx[64]; + char newpfx[64], infopfx[ARRAY_SIZE(newpfx) + 1]; + char error_type[120]; printk("%sMIDR: 0x%016llx\n", pfx, proc->midr); @@ -289,9 +281,15 @@ void cper_print_proc_arm(const char *pfx, newpfx); } - printk("%serror_type: %d, %s\n", newpfx, err_info->type, - err_info->type < ARRAY_SIZE(cper_proc_error_type_strs) ? - cper_proc_error_type_strs[err_info->type] : "unknown"); + cper_bits_to_str(error_type, sizeof(error_type), + FIELD_GET(CPER_ARM_ERR_TYPE_MASK, err_info->type), + cper_proc_error_type_strs, + ARRAY_SIZE(cper_proc_error_type_strs)); + + printk("%serror_type: 0x%02x: %s%s\n", newpfx, err_info->type, + error_type, + (err_info->type & ~CPER_ARM_ERR_TYPE_MASK) ? " with reserved bit(s)" : ""); + if (err_info->validation_bits & CPER_ARM_INFO_VALID_ERR_INFO) { printk("%serror_info: 0x%016llx\n", newpfx, err_info->error_info); diff --git a/drivers/firmware/efi/cper.c b/drivers/firmware/efi/cper.c index 928409199a1a..0232bd040f61 100644 --- a/drivers/firmware/efi/cper.c +++ b/drivers/firmware/efi/cper.c @@ -12,6 +12,7 @@ * Specification version 2.4. */ +#include <linux/bitmap.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/time.h> @@ -69,7 +70,7 @@ const char *cper_severity_str(unsigned int severity) } EXPORT_SYMBOL_GPL(cper_severity_str); -/* +/** * cper_print_bits - print strings for set bits * @pfx: prefix for each line, including log level and prefix string * @bits: bit mask @@ -106,6 +107,65 @@ void cper_print_bits(const char *pfx, unsigned int bits, printk("%s\n", buf); } +/** + * cper_bits_to_str - return a string for set bits + * @buf: buffer to store the output string + * @buf_size: size of the output string buffer + * @bits: bit mask + * @strs: string array, indexed by bit position + * @strs_size: size of the string array: @strs + * + * Add to @buf the bitmask in hexadecimal. Then, for each set bit in @bits, + * add the corresponding string describing the bit in @strs to @buf. + * + * A typical example is:: + * + * const char * const bits[] = { + * "bit 3 name", + * "bit 4 name", + * "bit 5 name", + * }; + * char str[120]; + * unsigned int bitmask = BIT(3) | BIT(5); + * #define MASK GENMASK(5,3) + * + * cper_bits_to_str(str, sizeof(str), FIELD_GET(MASK, bitmask), + * bits, ARRAY_SIZE(bits)); + * + * The above code fills the string ``str`` with ``bit 3 name|bit 5 name``. + * + * Return: number of bytes stored or an error code if lower than zero. + */ +int cper_bits_to_str(char *buf, int buf_size, unsigned long bits, + const char * const strs[], unsigned int strs_size) +{ + int len = buf_size; + char *str = buf; + int i, size; + + *buf = '\0'; + + for_each_set_bit(i, &bits, strs_size) { + if (!(bits & BIT_ULL(i))) + continue; + + if (*buf && len > 0) { + *str = '|'; + len--; + str++; + } + + size = strscpy(str, strs[i], len); + if (size < 0) + return size; + + len -= size; + str += size; + } + return len - buf_size; +} +EXPORT_SYMBOL_GPL(cper_bits_to_str); + static const char * const proc_type_strs[] = { "IA32/X64", "IA64", diff --git a/drivers/firmware/efi/libstub/efi-stub.c b/drivers/firmware/efi/libstub/efi-stub.c index 874f63b4a383..9cb814c5ba1b 100644 --- a/drivers/firmware/efi/libstub/efi-stub.c +++ b/drivers/firmware/efi/libstub/efi-stub.c @@ -56,7 +56,7 @@ static struct screen_info *setup_graphics(void) { struct screen_info *si, tmp = {}; - if (efi_setup_gop(&tmp) != EFI_SUCCESS) + if (efi_setup_graphics(&tmp, NULL) != EFI_SUCCESS) return NULL; si = alloc_screen_info(); diff --git a/drivers/firmware/efi/libstub/efistub.h b/drivers/firmware/efi/libstub/efistub.h index f5ba032863a9..b2fb0c3fa721 100644 --- a/drivers/firmware/efi/libstub/efistub.h +++ b/drivers/firmware/efi/libstub/efistub.h @@ -34,6 +34,9 @@ #define EFI_ALLOC_LIMIT ULONG_MAX #endif +struct edid_info; +struct screen_info; + extern bool efi_no5lvl; extern bool efi_nochunk; extern bool efi_nokaslr; @@ -578,6 +581,32 @@ union efi_graphics_output_protocol { } mixed_mode; }; +typedef union efi_edid_discovered_protocol efi_edid_discovered_protocol_t; + +union efi_edid_discovered_protocol { + struct { + u32 size_of_edid; + u8 *edid; + }; + struct { + u32 size_of_edid; + u32 edid; + } mixed_mode; +}; + +typedef union efi_edid_active_protocol efi_edid_active_protocol_t; + +union efi_edid_active_protocol { + struct { + u32 size_of_edid; + u8 *edid; + }; + struct { + u32 size_of_edid; + u32 edid; + } mixed_mode; +}; + typedef union { struct { u32 revision; @@ -1085,7 +1114,7 @@ efi_status_t efi_parse_options(char const *cmdline); void efi_parse_option_graphics(char *option); -efi_status_t efi_setup_gop(struct screen_info *si); +efi_status_t efi_setup_graphics(struct screen_info *si, struct edid_info *edid); efi_status_t handle_cmdline_files(efi_loaded_image_t *image, const efi_char16_t *optstr, diff --git a/drivers/firmware/efi/libstub/gop.c b/drivers/firmware/efi/libstub/gop.c index 3785fb4986b4..72d74436a7a4 100644 --- a/drivers/firmware/efi/libstub/gop.c +++ b/drivers/firmware/efi/libstub/gop.c @@ -12,6 +12,7 @@ #include <linux/string.h> #include <asm/efi.h> #include <asm/setup.h> +#include <video/edid.h> #include "efistub.h" @@ -367,24 +368,31 @@ static void find_bits(u32 mask, u8 *pos, u8 *size) *size = __fls(mask) - *pos + 1; } -static void -setup_pixel_info(struct screen_info *si, u32 pixels_per_scan_line, - efi_pixel_bitmask_t pixel_info, int pixel_format) +static void setup_screen_info(struct screen_info *si, const efi_graphics_output_protocol_t *gop) { - if (pixel_format == PIXEL_BIT_MASK) { - find_bits(pixel_info.red_mask, - &si->red_pos, &si->red_size); - find_bits(pixel_info.green_mask, - &si->green_pos, &si->green_size); - find_bits(pixel_info.blue_mask, - &si->blue_pos, &si->blue_size); - find_bits(pixel_info.reserved_mask, - &si->rsvd_pos, &si->rsvd_size); - si->lfb_depth = si->red_size + si->green_size + - si->blue_size + si->rsvd_size; - si->lfb_linelength = (pixels_per_scan_line * si->lfb_depth) / 8; + const efi_graphics_output_protocol_mode_t *mode = efi_table_attr(gop, mode); + const efi_graphics_output_mode_info_t *info = efi_table_attr(mode, info); + + si->orig_video_isVGA = VIDEO_TYPE_EFI; + + si->lfb_width = info->horizontal_resolution; + si->lfb_height = info->vertical_resolution; + + efi_set_u64_split(efi_table_attr(mode, frame_buffer_base), + &si->lfb_base, &si->ext_lfb_base); + if (si->ext_lfb_base) + si->capabilities |= VIDEO_CAPABILITY_64BIT_BASE; + si->pages = 1; + + if (info->pixel_format == PIXEL_BIT_MASK) { + find_bits(info->pixel_information.red_mask, &si->red_pos, &si->red_size); + find_bits(info->pixel_information.green_mask, &si->green_pos, &si->green_size); + find_bits(info->pixel_information.blue_mask, &si->blue_pos, &si->blue_size); + find_bits(info->pixel_information.reserved_mask, &si->rsvd_pos, &si->rsvd_size); + si->lfb_depth = si->red_size + si->green_size + si->blue_size + si->rsvd_size; + si->lfb_linelength = (info->pixels_per_scan_line * si->lfb_depth) / 8; } else { - if (pixel_format == PIXEL_RGB_RESERVED_8BIT_PER_COLOR) { + if (info->pixel_format == PIXEL_RGB_RESERVED_8BIT_PER_COLOR) { si->red_pos = 0; si->blue_pos = 16; } else /* PIXEL_BGR_RESERVED_8BIT_PER_COLOR */ { @@ -394,20 +402,33 @@ setup_pixel_info(struct screen_info *si, u32 pixels_per_scan_line, si->green_pos = 8; si->rsvd_pos = 24; - si->red_size = si->green_size = - si->blue_size = si->rsvd_size = 8; - + si->red_size = 8; + si->green_size = 8; + si->blue_size = 8; + si->rsvd_size = 8; si->lfb_depth = 32; - si->lfb_linelength = pixels_per_scan_line * 4; + si->lfb_linelength = info->pixels_per_scan_line * 4; } + + si->lfb_size = si->lfb_linelength * si->lfb_height; + si->capabilities |= VIDEO_CAPABILITY_SKIP_QUIRKS; } -static efi_graphics_output_protocol_t *find_gop(unsigned long num, - const efi_handle_t handles[]) +static void setup_edid_info(struct edid_info *edid, u32 gop_size_of_edid, u8 *gop_edid) +{ + if (!gop_edid || gop_size_of_edid < 128) + memset(edid->dummy, 0, sizeof(edid->dummy)); + else + memcpy(edid->dummy, gop_edid, min(gop_size_of_edid, sizeof(edid->dummy))); +} + +static efi_handle_t find_handle_with_primary_gop(unsigned long num, const efi_handle_t handles[], + efi_graphics_output_protocol_t **found_gop) { efi_graphics_output_protocol_t *first_gop; - efi_handle_t h; + efi_handle_t h, first_gop_handle; + first_gop_handle = NULL; first_gop = NULL; for_each_efi_handle(h, handles, num) { @@ -442,21 +463,25 @@ static efi_graphics_output_protocol_t *find_gop(unsigned long num, */ status = efi_bs_call(handle_protocol, h, &EFI_CONSOLE_OUT_DEVICE_GUID, &dummy); - if (status == EFI_SUCCESS) - return gop; - - if (!first_gop) + if (status == EFI_SUCCESS) { + if (found_gop) + *found_gop = gop; + return h; + } else if (!first_gop_handle) { + first_gop_handle = h; first_gop = gop; + } } - return first_gop; + if (found_gop) + *found_gop = first_gop; + return first_gop_handle; } -efi_status_t efi_setup_gop(struct screen_info *si) +efi_status_t efi_setup_graphics(struct screen_info *si, struct edid_info *edid) { efi_handle_t *handles __free(efi_pool) = NULL; - efi_graphics_output_protocol_mode_t *mode; - efi_graphics_output_mode_info_t *info; + efi_handle_t handle; efi_graphics_output_protocol_t *gop; efi_status_t status; unsigned long num; @@ -467,35 +492,41 @@ efi_status_t efi_setup_gop(struct screen_info *si) if (status != EFI_SUCCESS) return status; - gop = find_gop(num, handles); - if (!gop) + handle = find_handle_with_primary_gop(num, handles, &gop); + if (!handle) return EFI_NOT_FOUND; /* Change mode if requested */ set_mode(gop); /* EFI framebuffer */ - mode = efi_table_attr(gop, mode); - info = efi_table_attr(mode, info); - - si->orig_video_isVGA = VIDEO_TYPE_EFI; - - si->lfb_width = info->horizontal_resolution; - si->lfb_height = info->vertical_resolution; - - efi_set_u64_split(efi_table_attr(mode, frame_buffer_base), - &si->lfb_base, &si->ext_lfb_base); - if (si->ext_lfb_base) - si->capabilities |= VIDEO_CAPABILITY_64BIT_BASE; - - si->pages = 1; - - setup_pixel_info(si, info->pixels_per_scan_line, - info->pixel_information, info->pixel_format); - - si->lfb_size = si->lfb_linelength * si->lfb_height; + if (si) + setup_screen_info(si, gop); + + /* Display EDID for primary GOP */ + if (edid) { + efi_edid_discovered_protocol_t *discovered_edid; + efi_edid_active_protocol_t *active_edid; + u32 gop_size_of_edid = 0; + u8 *gop_edid = NULL; + + status = efi_bs_call(handle_protocol, handle, &EFI_EDID_ACTIVE_PROTOCOL_GUID, + (void **)&active_edid); + if (status == EFI_SUCCESS) { + gop_size_of_edid = active_edid->size_of_edid; + gop_edid = active_edid->edid; + } else { + status = efi_bs_call(handle_protocol, handle, + &EFI_EDID_DISCOVERED_PROTOCOL_GUID, + (void **)&discovered_edid); + if (status == EFI_SUCCESS) { + gop_size_of_edid = discovered_edid->size_of_edid; + gop_edid = discovered_edid->edid; + } + } - si->capabilities |= VIDEO_CAPABILITY_SKIP_QUIRKS; + setup_edid_info(edid, gop_size_of_edid, gop_edid); + } return EFI_SUCCESS; } diff --git a/drivers/firmware/efi/libstub/x86-stub.c b/drivers/firmware/efi/libstub/x86-stub.c index 761121a77f9e..cef32e2c82d8 100644 --- a/drivers/firmware/efi/libstub/x86-stub.c +++ b/drivers/firmware/efi/libstub/x86-stub.c @@ -203,6 +203,104 @@ static void retrieve_apple_device_properties(struct boot_params *boot_params) } } +struct smbios_entry_point { + u8 anchor[4]; + u8 ep_checksum; + u8 ep_length; + u8 major_version; + u8 minor_version; + u16 max_size_entry; + u8 ep_rev; + u8 reserved[5]; + + struct __packed { + u8 anchor[5]; + u8 checksum; + u16 st_length; + u32 st_address; + u16 number_of_entries; + u8 bcd_rev; + } intm; +}; + +static bool verify_ep_checksum(const void *ptr, int length) +{ + u8 sum = 0; + + for (int i = 0; i < length; i++) + sum += ((u8 *)ptr)[i]; + + return sum == 0; +} + +static bool verify_ep_integrity(const struct smbios_entry_point *ep) +{ + if (memcmp(ep->anchor, "_SM_", sizeof(ep->anchor)) != 0) + return false; + + if (memcmp(ep->intm.anchor, "_DMI_", sizeof(ep->intm.anchor)) != 0) + return false; + + if (!verify_ep_checksum(ep, ep->ep_length) || + !verify_ep_checksum(&ep->intm, sizeof(ep->intm))) + return false; + + return true; +} + +static const struct efi_smbios_record *search_record(void *table, u32 length, + u8 type) +{ + const u8 *p, *end; + + p = (u8 *)table; + end = p + length; + + while (p + sizeof(struct efi_smbios_record) < end) { + const struct efi_smbios_record *hdr = + (struct efi_smbios_record *)p; + const u8 *next; + + if (hdr->type == type) + return hdr; + + /* Type 127 = End-of-Table */ + if (hdr->type == 0x7F) + return NULL; + + /* Jumping to the unformed section */ + next = p + hdr->length; + + /* Unformed section ends with 0000h */ + while ((next[0] != 0 || next[1] != 0) && next + 1 < end) + next++; + + next += 2; + p = next; + } + + return NULL; +} + +static const struct efi_smbios_record *get_table_record(u8 type) +{ + const struct smbios_entry_point *ep; + + /* + * Locate the legacy 32-bit SMBIOS entrypoint in memory, and parse it + * directly. Needed by some Macs that do not implement the EFI protocol. + */ + ep = get_efi_config_table(SMBIOS_TABLE_GUID); + if (!ep) + return NULL; + + if (!verify_ep_integrity(ep)) + return NULL; + + return search_record((void *)(unsigned long)ep->intm.st_address, + ep->intm.st_length, type); +} + static bool apple_match_product_name(void) { static const char type1_product_matches[][15] = { @@ -218,7 +316,8 @@ static bool apple_match_product_name(void) const struct efi_smbios_type1_record *record; const u8 *product; - record = (struct efi_smbios_type1_record *)efi_get_smbios_record(1); + record = (struct efi_smbios_type1_record *) + (efi_get_smbios_record(1) ?: get_table_record(1)); if (!record) return false; @@ -388,8 +487,9 @@ static void setup_quirks(struct boot_params *boot_params) static void setup_graphics(struct boot_params *boot_params) { struct screen_info *si = memset(&boot_params->screen_info, 0, sizeof(*si)); + struct edid_info *edid = memset(&boot_params->edid_info, 0, sizeof(*edid)); - efi_setup_gop(si); + efi_setup_graphics(si, edid); } static void __noreturn efi_exit(efi_handle_t handle, efi_status_t status) diff --git a/drivers/firmware/efi/memattr.c b/drivers/firmware/efi/memattr.c index c38b1a335590..e727cc5909cb 100644 --- a/drivers/firmware/efi/memattr.c +++ b/drivers/firmware/efi/memattr.c @@ -19,19 +19,19 @@ unsigned long __ro_after_init efi_mem_attr_table = EFI_INVALID_TABLE_ADDR; * Reserve the memory associated with the Memory Attributes configuration * table, if it exists. */ -int __init efi_memattr_init(void) +void __init efi_memattr_init(void) { efi_memory_attributes_table_t *tbl; unsigned long size; if (efi_mem_attr_table == EFI_INVALID_TABLE_ADDR) - return 0; + return; tbl = early_memremap(efi_mem_attr_table, sizeof(*tbl)); if (!tbl) { pr_err("Failed to map EFI Memory Attributes table @ 0x%lx\n", efi_mem_attr_table); - return -ENOMEM; + return; } if (tbl->version > 2) { @@ -61,7 +61,6 @@ int __init efi_memattr_init(void) unmap: early_memunmap(tbl, sizeof(*tbl)); - return 0; } /* diff --git a/drivers/firmware/efi/riscv-runtime.c b/drivers/firmware/efi/riscv-runtime.c index fa71cd898120..66f584a228d0 100644 --- a/drivers/firmware/efi/riscv-runtime.c +++ b/drivers/firmware/efi/riscv-runtime.c @@ -14,18 +14,18 @@ #include <linux/io.h> #include <linux/memblock.h> #include <linux/mm_types.h> +#include <linux/pgalloc.h> +#include <linux/pgtable.h> #include <linux/preempt.h> #include <linux/rbtree.h> #include <linux/rwsem.h> #include <linux/sched.h> #include <linux/slab.h> #include <linux/spinlock.h> -#include <linux/pgtable.h> #include <asm/cacheflush.h> #include <asm/efi.h> #include <asm/mmu.h> -#include <asm/pgalloc.h> static bool __init efi_virtmap_init(void) { @@ -36,20 +36,12 @@ static bool __init efi_virtmap_init(void) init_new_context(NULL, &efi_mm); for_each_efi_memory_desc(md) { - phys_addr_t phys = md->phys_addr; - int ret; - if (!(md->attribute & EFI_MEMORY_RUNTIME)) continue; if (md->virt_addr == U64_MAX) return false; - ret = efi_create_mapping(&efi_mm, md); - if (ret) { - pr_warn(" EFI remap %pa: failed to create mapping (%d)\n", - &phys, ret); - return false; - } + efi_create_mapping(&efi_mm, md); } if (efi_memattr_apply_permissions(&efi_mm, efi_set_mapping_permissions)) diff --git a/drivers/firmware/efi/stmm/mm_communication.h b/drivers/firmware/efi/stmm/mm_communication.h index 52a1f32cd1eb..06e7663f96dc 100644 --- a/drivers/firmware/efi/stmm/mm_communication.h +++ b/drivers/firmware/efi/stmm/mm_communication.h @@ -32,7 +32,7 @@ /** * struct efi_mm_communicate_header - Header used for SMM variable communication - + * * @header_guid: header use for disambiguation of content * @message_len: length of the message. Does not include the size of the * header @@ -111,7 +111,7 @@ struct efi_mm_communicate_header { /** * struct smm_variable_communicate_header - Used for SMM variable communication - + * * @function: function to call in Smm. * @ret_status: return status * @data: payload @@ -128,7 +128,7 @@ struct smm_variable_communicate_header { /** * struct smm_variable_access - Used to communicate with StMM by * SetVariable and GetVariable. - + * * @guid: vendor GUID * @data_size: size of EFI variable data * @name_size: size of EFI name diff --git a/drivers/firmware/imx/imx-scu-irq.c b/drivers/firmware/imx/imx-scu-irq.c index 6125cccc9ba7..a68d38f89254 100644 --- a/drivers/firmware/imx/imx-scu-irq.c +++ b/drivers/firmware/imx/imx-scu-irq.c @@ -203,6 +203,18 @@ int imx_scu_enable_general_irq_channel(struct device *dev) struct mbox_chan *ch; int ret = 0, i = 0; + if (!of_parse_phandle_with_args(dev->of_node, "mboxes", + "#mbox-cells", 0, &spec)) { + i = of_alias_get_id(spec.np, "mu"); + of_node_put(spec.np); + } + + /* use mu1 as general mu irq channel if failed */ + if (i < 0) + i = 1; + + mu_resource_id = IMX_SC_R_MU_0A + i; + ret = imx_scu_get_handle(&imx_sc_irq_ipc_handle); if (ret) return ret; @@ -214,27 +226,16 @@ int imx_scu_enable_general_irq_channel(struct device *dev) cl->dev = dev; cl->rx_callback = imx_scu_irq_callback; + INIT_WORK(&imx_sc_irq_work, imx_scu_irq_work_handler); + /* SCU general IRQ uses general interrupt channel 3 */ ch = mbox_request_channel_byname(cl, "gip3"); if (IS_ERR(ch)) { ret = PTR_ERR(ch); dev_err(dev, "failed to request mbox chan gip3, ret %d\n", ret); - devm_kfree(dev, cl); - return ret; + goto free_cl; } - INIT_WORK(&imx_sc_irq_work, imx_scu_irq_work_handler); - - if (!of_parse_phandle_with_args(dev->of_node, "mboxes", - "#mbox-cells", 0, &spec)) - i = of_alias_get_id(spec.np, "mu"); - - /* use mu1 as general mu irq channel if failed */ - if (i < 0) - i = 1; - - mu_resource_id = IMX_SC_R_MU_0A + i; - /* Create directory under /sysfs/firmware */ wakeup_obj = kobject_create_and_add("scu_wakeup_source", firmware_kobj); if (!wakeup_obj) { @@ -253,7 +254,8 @@ int imx_scu_enable_general_irq_channel(struct device *dev) free_ch: mbox_free_channel(ch); +free_cl: + devm_kfree(dev, cl); return ret; } -EXPORT_SYMBOL(imx_scu_enable_general_irq_channel); diff --git a/drivers/firmware/imx/imx-scu.c b/drivers/firmware/imx/imx-scu.c index 8c28e25ddc8a..67b267a7408a 100644 --- a/drivers/firmware/imx/imx-scu.c +++ b/drivers/firmware/imx/imx-scu.c @@ -73,9 +73,9 @@ static int imx_sc_linux_errmap[IMX_SC_ERR_LAST] = { -EACCES, /* IMX_SC_ERR_NOACCESS */ -EACCES, /* IMX_SC_ERR_LOCKED */ -ERANGE, /* IMX_SC_ERR_UNAVAILABLE */ - -EEXIST, /* IMX_SC_ERR_NOTFOUND */ - -EPERM, /* IMX_SC_ERR_NOPOWER */ - -EPIPE, /* IMX_SC_ERR_IPC */ + -ENOENT, /* IMX_SC_ERR_NOTFOUND */ + -ENODEV, /* IMX_SC_ERR_NOPOWER */ + -ECOMM, /* IMX_SC_ERR_IPC */ -EBUSY, /* IMX_SC_ERR_BUSY */ -EIO, /* IMX_SC_ERR_FAIL */ }; @@ -324,7 +324,9 @@ static int imx_scu_probe(struct platform_device *pdev) } sc_ipc->dev = dev; - mutex_init(&sc_ipc->lock); + ret = devm_mutex_init(dev, &sc_ipc->lock); + if (ret) + return ret; init_completion(&sc_ipc->done); imx_sc_ipc_handle = sc_ipc; @@ -352,6 +354,7 @@ static struct platform_driver imx_scu_driver = { .driver = { .name = "imx-scu", .of_match_table = imx_scu_match, + .suppress_bind_attrs = true, }, .probe = imx_scu_probe, }; diff --git a/drivers/firmware/qcom/qcom_scm.c b/drivers/firmware/qcom/qcom_scm.c index e777b7cb9b12..1a6f85e463e0 100644 --- a/drivers/firmware/qcom/qcom_scm.c +++ b/drivers/firmware/qcom/qcom_scm.c @@ -2018,21 +2018,6 @@ static const struct of_device_id qcom_scm_qseecom_allowlist[] __maybe_unused = { { } }; -static bool qcom_scm_qseecom_machine_is_allowed(void) -{ - struct device_node *np; - bool match; - - np = of_find_node_by_path("/"); - if (!np) - return false; - - match = of_match_node(qcom_scm_qseecom_allowlist, np); - of_node_put(np); - - return match; -} - static void qcom_scm_qseecom_free(void *data) { struct platform_device *qseecom_dev = data; @@ -2064,7 +2049,7 @@ static int qcom_scm_qseecom_init(struct qcom_scm *scm) dev_info(scm->dev, "qseecom: found qseecom with version 0x%x\n", version); - if (!qcom_scm_qseecom_machine_is_allowed()) { + if (!of_machine_device_match(qcom_scm_qseecom_allowlist)) { dev_info(scm->dev, "qseecom: untested machine, skipping\n"); return 0; } diff --git a/drivers/firmware/ti_sci.c b/drivers/firmware/ti_sci.c index 49fd2ae01055..e027a2bd8f26 100644 --- a/drivers/firmware/ti_sci.c +++ b/drivers/firmware/ti_sci.c @@ -398,6 +398,9 @@ static void ti_sci_put_one_xfer(struct ti_sci_xfers_info *minfo, static inline int ti_sci_do_xfer(struct ti_sci_info *info, struct ti_sci_xfer *xfer) { + struct ti_sci_msg_hdr *hdr = (struct ti_sci_msg_hdr *)xfer->tx_message.buf; + bool response_expected = !!(hdr->flags & (TI_SCI_FLAG_REQ_ACK_ON_PROCESSED | + TI_SCI_FLAG_REQ_ACK_ON_RECEIVED)); int ret; int timeout; struct device *dev = info->dev; @@ -409,12 +412,12 @@ static inline int ti_sci_do_xfer(struct ti_sci_info *info, ret = 0; - if (system_state <= SYSTEM_RUNNING) { + if (response_expected && system_state <= SYSTEM_RUNNING) { /* And we wait for the response. */ timeout = msecs_to_jiffies(info->desc->max_rx_timeout_ms); if (!wait_for_completion_timeout(&xfer->done, timeout)) ret = -ETIMEDOUT; - } else { + } else if (response_expected) { /* * If we are !running, we cannot use wait_for_completion_timeout * during noirq phase, so we must manually poll the completion. @@ -1670,6 +1673,9 @@ fail: static int ti_sci_cmd_prepare_sleep(const struct ti_sci_handle *handle, u8 mode, u32 ctx_lo, u32 ctx_hi, u32 debug_flags) { + u32 msg_flags = mode == TISCI_MSG_VALUE_SLEEP_MODE_PARTIAL_IO ? + TI_SCI_FLAG_REQ_GENERIC_NORESPONSE : + TI_SCI_FLAG_REQ_ACK_ON_PROCESSED; struct ti_sci_info *info; struct ti_sci_msg_req_prepare_sleep *req; struct ti_sci_msg_hdr *resp; @@ -1686,7 +1692,7 @@ static int ti_sci_cmd_prepare_sleep(const struct ti_sci_handle *handle, u8 mode, dev = info->dev; xfer = ti_sci_get_one_xfer(info, TI_SCI_MSG_PREPARE_SLEEP, - TI_SCI_FLAG_REQ_ACK_ON_PROCESSED, + msg_flags, sizeof(*req), sizeof(*resp)); if (IS_ERR(xfer)) { ret = PTR_ERR(xfer); @@ -1706,11 +1712,12 @@ static int ti_sci_cmd_prepare_sleep(const struct ti_sci_handle *handle, u8 mode, goto fail; } - resp = (struct ti_sci_msg_hdr *)xfer->xfer_buf; - - if (!ti_sci_is_response_ack(resp)) { - dev_err(dev, "Failed to prepare sleep\n"); - ret = -ENODEV; + if (msg_flags == TI_SCI_FLAG_REQ_ACK_ON_PROCESSED) { + resp = (struct ti_sci_msg_hdr *)xfer->xfer_buf; + if (!ti_sci_is_response_ack(resp)) { + dev_err(dev, "Failed to prepare sleep\n"); + ret = -ENODEV; + } } fail: @@ -3664,6 +3671,78 @@ devm_ti_sci_get_resource(const struct ti_sci_handle *handle, struct device *dev, } EXPORT_SYMBOL_GPL(devm_ti_sci_get_resource); +/* + * Iterate all device nodes that have a wakeup-source property and check if one + * of the possible phandles points to a Partial-IO system state. If it + * does resolve the device node to an actual device and check if wakeup is + * enabled. + */ +static bool ti_sci_partial_io_wakeup_enabled(struct ti_sci_info *info) +{ + struct device_node *wakeup_node = NULL; + + for_each_node_with_property(wakeup_node, "wakeup-source") { + struct of_phandle_iterator it; + int err; + + of_for_each_phandle(&it, err, wakeup_node, "wakeup-source", NULL, 0) { + struct platform_device *pdev; + bool may_wakeup; + + /* + * Continue if idle-state-name is not off-wake. Return + * value is the index of the string which should be 0 if + * off-wake is present. + */ + if (of_property_match_string(it.node, "idle-state-name", "off-wake")) + continue; + + pdev = of_find_device_by_node(wakeup_node); + if (!pdev) + continue; + + may_wakeup = device_may_wakeup(&pdev->dev); + put_device(&pdev->dev); + + if (may_wakeup) { + dev_dbg(info->dev, "%pOF identified as wakeup source for Partial-IO\n", + wakeup_node); + of_node_put(it.node); + of_node_put(wakeup_node); + return true; + } + } + } + + return false; +} + +static int ti_sci_sys_off_handler(struct sys_off_data *data) +{ + struct ti_sci_info *info = data->cb_data; + const struct ti_sci_handle *handle = &info->handle; + bool enter_partial_io = ti_sci_partial_io_wakeup_enabled(info); + int ret; + + if (!enter_partial_io) + return NOTIFY_DONE; + + dev_info(info->dev, "Entering Partial-IO because a powered wakeup-enabled device was found.\n"); + + ret = ti_sci_cmd_prepare_sleep(handle, TISCI_MSG_VALUE_SLEEP_MODE_PARTIAL_IO, 0, 0, 0); + if (ret) { + dev_err(info->dev, + "Failed to enter Partial-IO %pe, trying to do an emergency restart\n", + ERR_PTR(ret)); + emergency_restart(); + } + + mdelay(5000); + emergency_restart(); + + return NOTIFY_DONE; +} + static int tisci_reboot_handler(struct sys_off_data *data) { struct ti_sci_info *info = data->cb_data; @@ -3706,7 +3785,7 @@ static int ti_sci_prepare_system_suspend(struct ti_sci_info *info) } } -static int __maybe_unused ti_sci_suspend(struct device *dev) +static int ti_sci_suspend(struct device *dev) { struct ti_sci_info *info = dev_get_drvdata(dev); struct device *cpu_dev, *cpu_dev_max = NULL; @@ -3746,19 +3825,21 @@ static int __maybe_unused ti_sci_suspend(struct device *dev) return 0; } -static int __maybe_unused ti_sci_suspend_noirq(struct device *dev) +static int ti_sci_suspend_noirq(struct device *dev) { struct ti_sci_info *info = dev_get_drvdata(dev); int ret = 0; - ret = ti_sci_cmd_set_io_isolation(&info->handle, TISCI_MSG_VALUE_IO_ENABLE); - if (ret) - return ret; + if (info->fw_caps & MSG_FLAG_CAPS_IO_ISOLATION) { + ret = ti_sci_cmd_set_io_isolation(&info->handle, TISCI_MSG_VALUE_IO_ENABLE); + if (ret) + return ret; + } return 0; } -static int __maybe_unused ti_sci_resume_noirq(struct device *dev) +static int ti_sci_resume_noirq(struct device *dev) { struct ti_sci_info *info = dev_get_drvdata(dev); int ret = 0; @@ -3767,9 +3848,11 @@ static int __maybe_unused ti_sci_resume_noirq(struct device *dev) u8 pin; u8 mode; - ret = ti_sci_cmd_set_io_isolation(&info->handle, TISCI_MSG_VALUE_IO_DISABLE); - if (ret) - return ret; + if (info->fw_caps & MSG_FLAG_CAPS_IO_ISOLATION) { + ret = ti_sci_cmd_set_io_isolation(&info->handle, TISCI_MSG_VALUE_IO_DISABLE); + if (ret) + return ret; + } ret = ti_sci_msg_cmd_lpm_wake_reason(&info->handle, &source, &time, &pin, &mode); /* Do not fail to resume on error as the wake reason is not critical */ @@ -3780,7 +3863,7 @@ static int __maybe_unused ti_sci_resume_noirq(struct device *dev) return 0; } -static void __maybe_unused ti_sci_pm_complete(struct device *dev) +static void ti_sci_pm_complete(struct device *dev) { struct ti_sci_info *info = dev_get_drvdata(dev); @@ -3791,12 +3874,10 @@ static void __maybe_unused ti_sci_pm_complete(struct device *dev) } static const struct dev_pm_ops ti_sci_pm_ops = { -#ifdef CONFIG_PM_SLEEP - .suspend = ti_sci_suspend, - .suspend_noirq = ti_sci_suspend_noirq, - .resume_noirq = ti_sci_resume_noirq, - .complete = ti_sci_pm_complete, -#endif + .suspend = pm_sleep_ptr(ti_sci_suspend), + .suspend_noirq = pm_sleep_ptr(ti_sci_suspend_noirq), + .resume_noirq = pm_sleep_ptr(ti_sci_resume_noirq), + .complete = pm_sleep_ptr(ti_sci_pm_complete), }; /* Description for K2G */ @@ -3928,11 +4009,12 @@ static int ti_sci_probe(struct platform_device *pdev) } ti_sci_msg_cmd_query_fw_caps(&info->handle, &info->fw_caps); - dev_dbg(dev, "Detected firmware capabilities: %s%s%s%s\n", + dev_dbg(dev, "Detected firmware capabilities: %s%s%s%s%s\n", info->fw_caps & MSG_FLAG_CAPS_GENERIC ? "Generic" : "", info->fw_caps & MSG_FLAG_CAPS_LPM_PARTIAL_IO ? " Partial-IO" : "", info->fw_caps & MSG_FLAG_CAPS_LPM_DM_MANAGED ? " DM-Managed" : "", - info->fw_caps & MSG_FLAG_CAPS_LPM_ABORT ? " LPM-Abort" : "" + info->fw_caps & MSG_FLAG_CAPS_LPM_ABORT ? " LPM-Abort" : "", + info->fw_caps & MSG_FLAG_CAPS_IO_ISOLATION ? " IO-Isolation" : "" ); ti_sci_setup_ops(info); @@ -3943,6 +4025,19 @@ static int ti_sci_probe(struct platform_device *pdev) goto out; } + if (info->fw_caps & MSG_FLAG_CAPS_LPM_PARTIAL_IO) { + ret = devm_register_sys_off_handler(dev, + SYS_OFF_MODE_POWER_OFF, + SYS_OFF_PRIO_FIRMWARE, + ti_sci_sys_off_handler, + info); + if (ret) { + dev_err(dev, "Failed to register sys_off_handler %pe\n", + ERR_PTR(ret)); + goto out; + } + } + dev_info(dev, "ABI: %d.%d (firmware rev 0x%04x '%s')\n", info->handle.version.abi_major, info->handle.version.abi_minor, info->handle.version.firmware_revision, @@ -3952,7 +4047,13 @@ static int ti_sci_probe(struct platform_device *pdev) list_add_tail(&info->node, &ti_sci_list); mutex_unlock(&ti_sci_list_mutex); - return of_platform_populate(dev->of_node, NULL, NULL, dev); + ret = of_platform_populate(dev->of_node, NULL, NULL, dev); + if (ret) { + dev_err(dev, "platform_populate failed %pe\n", ERR_PTR(ret)); + goto out; + } + return 0; + out: if (!IS_ERR(info->chan_tx)) mbox_free_channel(info->chan_tx); diff --git a/drivers/firmware/ti_sci.h b/drivers/firmware/ti_sci.h index 701c416b2e78..91f234550c43 100644 --- a/drivers/firmware/ti_sci.h +++ b/drivers/firmware/ti_sci.h @@ -149,6 +149,7 @@ struct ti_sci_msg_req_reboot { * MSG_FLAG_CAPS_LPM_PARTIAL_IO: Partial IO in LPM * MSG_FLAG_CAPS_LPM_DM_MANAGED: LPM can be managed by DM * MSG_FLAG_CAPS_LPM_ABORT: Abort entry to LPM + * MSG_FLAG_CAPS_IO_ISOLATION: IO Isolation support * * Response to a generic message with message type TI_SCI_MSG_QUERY_FW_CAPS * providing currently available SOC/firmware capabilities. SoC that don't @@ -160,6 +161,7 @@ struct ti_sci_msg_resp_query_fw_caps { #define MSG_FLAG_CAPS_LPM_PARTIAL_IO TI_SCI_MSG_FLAG(4) #define MSG_FLAG_CAPS_LPM_DM_MANAGED TI_SCI_MSG_FLAG(5) #define MSG_FLAG_CAPS_LPM_ABORT TI_SCI_MSG_FLAG(9) +#define MSG_FLAG_CAPS_IO_ISOLATION TI_SCI_MSG_FLAG(7) #define MSG_MASK_CAPS_LPM GENMASK_ULL(4, 1) u64 fw_caps; } __packed; @@ -595,6 +597,11 @@ struct ti_sci_msg_resp_get_clock_freq { struct ti_sci_msg_req_prepare_sleep { struct ti_sci_msg_hdr hdr; +/* + * When sending prepare_sleep with MODE_PARTIAL_IO no response will be sent, + * no further steps are required. + */ +#define TISCI_MSG_VALUE_SLEEP_MODE_PARTIAL_IO 0x03 #define TISCI_MSG_VALUE_SLEEP_MODE_DM_MANAGED 0xfd u8 mode; u32 ctx_lo; diff --git a/drivers/firmware/xilinx/Makefile b/drivers/firmware/xilinx/Makefile index 875a53703c82..70f8f02f14a3 100644 --- a/drivers/firmware/xilinx/Makefile +++ b/drivers/firmware/xilinx/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 # Makefile for Xilinx firmwares -obj-$(CONFIG_ZYNQMP_FIRMWARE) += zynqmp.o +obj-$(CONFIG_ZYNQMP_FIRMWARE) += zynqmp.o zynqmp-ufs.o obj-$(CONFIG_ZYNQMP_FIRMWARE_DEBUG) += zynqmp-debug.o diff --git a/drivers/firmware/xilinx/zynqmp-debug.c b/drivers/firmware/xilinx/zynqmp-debug.c index 22853ae0efdf..36efb827f3da 100644 --- a/drivers/firmware/xilinx/zynqmp-debug.c +++ b/drivers/firmware/xilinx/zynqmp-debug.c @@ -3,6 +3,7 @@ * Xilinx Zynq MPSoC Firmware layer for debugfs APIs * * Copyright (C) 2014-2018 Xilinx, Inc. + * Copyright (C) 2022 - 2025 Advanced Micro Devices, Inc. * * Michal Simek <michal.simek@amd.com> * Davorin Mista <davorin.mista@aggios.com> @@ -38,6 +39,7 @@ static struct pm_api_info pm_api_list[] = { PM_API(PM_RELEASE_NODE), PM_API(PM_SET_REQUIREMENT), PM_API(PM_GET_API_VERSION), + PM_API(PM_GET_NODE_STATUS), PM_API(PM_REGISTER_NOTIFIER), PM_API(PM_RESET_ASSERT), PM_API(PM_RESET_GET_STATUS), @@ -167,6 +169,17 @@ static int process_api_request(u32 pm_id, u64 *pm_api_arg, u32 *pm_api_ret) pm_api_arg[3] ? pm_api_arg[3] : ZYNQMP_PM_REQUEST_ACK_BLOCKING); break; + case PM_GET_NODE_STATUS: + ret = zynqmp_pm_get_node_status(pm_api_arg[0], + &pm_api_ret[0], + &pm_api_ret[1], + &pm_api_ret[2]); + if (!ret) + sprintf(debugfs_buf, + "GET_NODE_STATUS:\n\tNodeId: %llu\n\tStatus: %u\n\tRequirements: %u\n\tUsage: %u\n", + pm_api_arg[0], pm_api_ret[0], + pm_api_ret[1], pm_api_ret[2]); + break; case PM_REGISTER_NOTIFIER: ret = zynqmp_pm_register_notifier(pm_api_arg[0], pm_api_arg[1] ? diff --git a/drivers/firmware/xilinx/zynqmp-ufs.c b/drivers/firmware/xilinx/zynqmp-ufs.c new file mode 100644 index 000000000000..85da8a822f3a --- /dev/null +++ b/drivers/firmware/xilinx/zynqmp-ufs.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Firmware Layer for UFS APIs + * + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#include <linux/firmware/xlnx-zynqmp.h> +#include <linux/module.h> + +/* Register Node IDs */ +#define PM_REGNODE_PMC_IOU_SLCR 0x30000002 /* PMC IOU SLCR */ +#define PM_REGNODE_EFUSE_CACHE 0x30000003 /* EFUSE Cache */ + +/* Register Offsets for PMC IOU SLCR */ +#define SRAM_CSR_OFFSET 0x104C /* SRAM Control and Status */ +#define TXRX_CFGRDY_OFFSET 0x1054 /* M-PHY TX-RX Config ready */ + +/* Masks for SRAM Control and Status Register */ +#define SRAM_CSR_INIT_DONE_MASK BIT(0) /* SRAM initialization done */ +#define SRAM_CSR_EXT_LD_DONE_MASK BIT(1) /* SRAM External load done */ +#define SRAM_CSR_BYPASS_MASK BIT(2) /* Bypass SRAM interface */ + +/* Mask to check M-PHY TX-RX configuration readiness */ +#define TX_RX_CFG_RDY_MASK GENMASK(3, 0) + +/* Register Offsets for EFUSE Cache */ +#define UFS_CAL_1_OFFSET 0xBE8 /* UFS Calibration Value */ + +/** + * zynqmp_pm_is_mphy_tx_rx_config_ready - check M-PHY TX-RX config readiness + * @is_ready: Store output status (true/false) + * + * Return: Returns 0 on success or error value on failure. + */ +int zynqmp_pm_is_mphy_tx_rx_config_ready(bool *is_ready) +{ + u32 regval; + int ret; + + if (!is_ready) + return -EINVAL; + + ret = zynqmp_pm_sec_read_reg(PM_REGNODE_PMC_IOU_SLCR, TXRX_CFGRDY_OFFSET, ®val); + if (ret) + return ret; + + regval &= TX_RX_CFG_RDY_MASK; + if (regval) + *is_ready = true; + else + *is_ready = false; + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_is_mphy_tx_rx_config_ready); + +/** + * zynqmp_pm_is_sram_init_done - check SRAM initialization + * @is_done: Store output status (true/false) + * + * Return: Returns 0 on success or error value on failure. + */ +int zynqmp_pm_is_sram_init_done(bool *is_done) +{ + u32 regval; + int ret; + + if (!is_done) + return -EINVAL; + + ret = zynqmp_pm_sec_read_reg(PM_REGNODE_PMC_IOU_SLCR, SRAM_CSR_OFFSET, ®val); + if (ret) + return ret; + + regval &= SRAM_CSR_INIT_DONE_MASK; + if (regval) + *is_done = true; + else + *is_done = false; + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_is_sram_init_done); + +/** + * zynqmp_pm_set_sram_bypass - Set SRAM bypass Control + * + * Return: Returns 0 on success or error value on failure. + */ +int zynqmp_pm_set_sram_bypass(void) +{ + u32 sram_csr; + int ret; + + ret = zynqmp_pm_sec_read_reg(PM_REGNODE_PMC_IOU_SLCR, SRAM_CSR_OFFSET, &sram_csr); + if (ret) + return ret; + + sram_csr &= ~SRAM_CSR_EXT_LD_DONE_MASK; + sram_csr |= SRAM_CSR_BYPASS_MASK; + + return zynqmp_pm_sec_mask_write_reg(PM_REGNODE_PMC_IOU_SLCR, SRAM_CSR_OFFSET, + GENMASK(2, 1), sram_csr); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_set_sram_bypass); + +/** + * zynqmp_pm_get_ufs_calibration_values - Read UFS calibration values + * @val: Store the calibration value + * + * Return: Returns 0 on success or error value on failure. + */ +int zynqmp_pm_get_ufs_calibration_values(u32 *val) +{ + return zynqmp_pm_sec_read_reg(PM_REGNODE_EFUSE_CACHE, UFS_CAL_1_OFFSET, val); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_get_ufs_calibration_values); diff --git a/drivers/firmware/xilinx/zynqmp.c b/drivers/firmware/xilinx/zynqmp.c index 02da3e48bc8f..ad811f40e059 100644 --- a/drivers/firmware/xilinx/zynqmp.c +++ b/drivers/firmware/xilinx/zynqmp.c @@ -3,7 +3,7 @@ * Xilinx Zynq MPSoC Firmware layer * * Copyright (C) 2014-2022 Xilinx, Inc. - * Copyright (C) 2022 - 2024, Advanced Micro Devices, Inc. + * Copyright (C) 2022 - 2025 Advanced Micro Devices, Inc. * * Michal Simek <michal.simek@amd.com> * Davorin Mista <davorin.mista@aggios.com> @@ -72,6 +72,15 @@ struct pm_api_feature_data { struct hlist_node hentry; }; +struct platform_fw_data { + /* + * Family code for platform. + */ + const u32 family_code; +}; + +static struct platform_fw_data *active_platform_fw_data; + static const struct mfd_cell firmware_devs[] = { { .name = "zynqmp_power_controller", @@ -464,8 +473,6 @@ int zynqmp_pm_invoke_fn(u32 pm_api_id, u32 *ret_payload, u32 num_args, ...) static u32 pm_api_version; static u32 pm_tz_version; -static u32 pm_family_code; -static u32 pm_sub_family_code; int zynqmp_pm_register_sgi(u32 sgi_num, u32 reset) { @@ -532,32 +539,18 @@ EXPORT_SYMBOL_GPL(zynqmp_pm_get_chipid); /** * zynqmp_pm_get_family_info() - Get family info of platform * @family: Returned family code value - * @subfamily: Returned sub-family code value * * Return: Returns status, either success or error+reason */ -int zynqmp_pm_get_family_info(u32 *family, u32 *subfamily) +int zynqmp_pm_get_family_info(u32 *family) { - u32 ret_payload[PAYLOAD_ARG_CNT]; - u32 idcode; - int ret; + if (!active_platform_fw_data) + return -ENODEV; - /* Check is family or sub-family code already received */ - if (pm_family_code && pm_sub_family_code) { - *family = pm_family_code; - *subfamily = pm_sub_family_code; - return 0; - } - - ret = zynqmp_pm_invoke_fn(PM_GET_CHIPID, ret_payload, 0); - if (ret < 0) - return ret; + if (!family) + return -EINVAL; - idcode = ret_payload[1]; - pm_family_code = FIELD_GET(FAMILY_CODE_MASK, idcode); - pm_sub_family_code = FIELD_GET(SUB_FAMILY_CODE_MASK, idcode); - *family = pm_family_code; - *subfamily = pm_sub_family_code; + *family = active_platform_fw_data->family_code; return 0; } @@ -1238,8 +1231,13 @@ int zynqmp_pm_pinctrl_set_config(const u32 pin, const u32 param, u32 value) { int ret; + u32 pm_family_code; + + ret = zynqmp_pm_get_family_info(&pm_family_code); + if (ret) + return ret; - if (pm_family_code == ZYNQMP_FAMILY_CODE && + if (pm_family_code == PM_ZYNQMP_FAMILY_CODE && param == PM_PINCTRL_CONFIG_TRI_STATE) { ret = zynqmp_pm_feature(PM_PINCTRL_CONFIG_PARAM_SET); if (ret < PM_PINCTRL_PARAM_SET_VERSION) { @@ -1414,6 +1412,45 @@ int zynqmp_pm_set_tcm_config(u32 node_id, enum rpu_tcm_comb tcm_mode) EXPORT_SYMBOL_GPL(zynqmp_pm_set_tcm_config); /** + * zynqmp_pm_get_node_status - PM call to request a node's current power state + * @node: ID of the component or sub-system in question + * @status: Current operating state of the requested node + * @requirements: Current requirements asserted on the node, + * used for slave nodes only. + * @usage: Usage information, used for slave nodes only: + * PM_USAGE_NO_MASTER - No master is currently using + * the node + * PM_USAGE_CURRENT_MASTER - Only requesting master is + * currently using the node + * PM_USAGE_OTHER_MASTER - Only other masters are + * currently using the node + * PM_USAGE_BOTH_MASTERS - Both the current and at least + * one other master is currently + * using the node + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_get_node_status(const u32 node, u32 *const status, + u32 *const requirements, u32 *const usage) +{ + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + if (!status || !requirements || !usage) + return -EINVAL; + + ret = zynqmp_pm_invoke_fn(PM_GET_NODE_STATUS, ret_payload, 1, node); + if (ret_payload[0] == XST_PM_SUCCESS) { + *status = ret_payload[1]; + *requirements = ret_payload[2]; + *usage = ret_payload[3]; + } + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_get_node_status); + +/** * zynqmp_pm_force_pwrdwn - PM call to request for another PU or subsystem to * be powered down forcefully * @node: Node ID of the targeted PU or subsystem @@ -1617,6 +1654,52 @@ int zynqmp_pm_get_feature_config(enum pm_feature_config_id id, } /** + * zynqmp_pm_sec_read_reg - PM call to securely read from given offset + * of the node + * @node_id: Node Id of the device + * @offset: Offset to be used (20-bit) + * @ret_value: Output data read from the given offset after + * firmware access policy is successfully enforced + * + * Return: Returns 0 on success or error value on failure + */ +int zynqmp_pm_sec_read_reg(u32 node_id, u32 offset, u32 *ret_value) +{ + u32 ret_payload[PAYLOAD_ARG_CNT]; + u32 count = 1; + int ret; + + if (!ret_value) + return -EINVAL; + + ret = zynqmp_pm_invoke_fn(PM_IOCTL, ret_payload, 4, node_id, IOCTL_READ_REG, + offset, count); + + *ret_value = ret_payload[1]; + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_sec_read_reg); + +/** + * zynqmp_pm_sec_mask_write_reg - PM call to securely write to given offset + * of the node + * @node_id: Node Id of the device + * @offset: Offset to be used (20-bit) + * @mask: Mask to be used + * @value: Value to be written + * + * Return: Returns 0 on success or error value on failure + */ +int zynqmp_pm_sec_mask_write_reg(const u32 node_id, const u32 offset, u32 mask, + u32 value) +{ + return zynqmp_pm_invoke_fn(PM_IOCTL, NULL, 5, node_id, IOCTL_MASK_WRITE_REG, + offset, mask, value); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_sec_mask_write_reg); + +/** * zynqmp_pm_set_sd_config - PM call to set value of SD config registers * @node: SD node ID * @config: The config type of SD registers @@ -2007,12 +2090,18 @@ static int zynqmp_firmware_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct zynqmp_devinfo *devinfo; + u32 pm_family_code; int ret; ret = get_set_conduit_method(dev->of_node); if (ret) return ret; + /* Get platform-specific firmware data from device tree match */ + active_platform_fw_data = (struct platform_fw_data *)device_get_match_data(dev); + if (!active_platform_fw_data) + return -EINVAL; + /* Get SiP SVC version number */ ret = zynqmp_pm_get_sip_svc_version(&sip_svc_version); if (ret) @@ -2045,8 +2134,8 @@ static int zynqmp_firmware_probe(struct platform_device *pdev) pr_info("%s Platform Management API v%d.%d\n", __func__, pm_api_version >> 16, pm_api_version & 0xFFFF); - /* Get the Family code and sub family code of platform */ - ret = zynqmp_pm_get_family_info(&pm_family_code, &pm_sub_family_code); + /* Get the Family code of platform */ + ret = zynqmp_pm_get_family_info(&pm_family_code); if (ret < 0) return ret; @@ -2073,7 +2162,7 @@ static int zynqmp_firmware_probe(struct platform_device *pdev) zynqmp_pm_api_debugfs_init(); - if (pm_family_code == VERSAL_FAMILY_CODE) { + if (pm_family_code != PM_ZYNQMP_FAMILY_CODE) { em_dev = platform_device_register_data(&pdev->dev, "xlnx_event_manager", -1, NULL, 0); if (IS_ERR(em_dev)) @@ -2113,9 +2202,22 @@ static void zynqmp_firmware_sync_state(struct device *dev) dev_warn(dev, "failed to release power management to firmware\n"); } +static const struct platform_fw_data platform_fw_data_versal = { + .family_code = PM_VERSAL_FAMILY_CODE, +}; + +static const struct platform_fw_data platform_fw_data_versal_net = { + .family_code = PM_VERSAL_NET_FAMILY_CODE, +}; + +static const struct platform_fw_data platform_fw_data_zynqmp = { + .family_code = PM_ZYNQMP_FAMILY_CODE, +}; + static const struct of_device_id zynqmp_firmware_of_match[] = { - {.compatible = "xlnx,zynqmp-firmware"}, - {.compatible = "xlnx,versal-firmware"}, + {.compatible = "xlnx,zynqmp-firmware", .data = &platform_fw_data_zynqmp}, + {.compatible = "xlnx,versal-firmware", .data = &platform_fw_data_versal}, + {.compatible = "xlnx,versal-net-firmware", .data = &platform_fw_data_versal_net}, {}, }; MODULE_DEVICE_TABLE(of, zynqmp_firmware_of_match); |
