| 1 | // SPDX-License-Identifier: GPL-2.0-only |
| 2 | /* |
| 3 | * AMD Secure Processor Seamless Firmware Servicing support. |
| 4 | * |
| 5 | * Copyright (C) 2025 Advanced Micro Devices, Inc. |
| 6 | * |
| 7 | * Author: Ashish Kalra <ashish.kalra@amd.com> |
| 8 | */ |
| 9 | |
| 10 | #include <linux/firmware.h> |
| 11 | |
| 12 | #include "sfs.h" |
| 13 | #include "sev-dev.h" |
| 14 | |
| 15 | #define SFS_DEFAULT_TIMEOUT (10 * MSEC_PER_SEC) |
| 16 | #define SFS_MAX_PAYLOAD_SIZE (2 * 1024 * 1024) |
| 17 | #define SFS_NUM_2MB_PAGES_CMDBUF (SFS_MAX_PAYLOAD_SIZE / PMD_SIZE) |
| 18 | #define SFS_NUM_PAGES_CMDBUF (SFS_MAX_PAYLOAD_SIZE / PAGE_SIZE) |
| 19 | |
| 20 | static DEFINE_MUTEX(sfs_ioctl_mutex); |
| 21 | |
| 22 | static struct sfs_misc_dev *misc_dev; |
| 23 | |
| 24 | static int send_sfs_cmd(struct sfs_device *sfs_dev, int msg) |
| 25 | { |
| 26 | int ret; |
| 27 | |
| 28 | sfs_dev->command_buf->hdr.status = 0; |
| 29 | sfs_dev->command_buf->hdr.sub_cmd_id = msg; |
| 30 | |
| 31 | ret = psp_extended_mailbox_cmd(psp: sfs_dev->psp, |
| 32 | SFS_DEFAULT_TIMEOUT, |
| 33 | req: (struct psp_ext_request *)sfs_dev->command_buf); |
| 34 | if (ret == -EIO) { |
| 35 | dev_dbg(sfs_dev->dev, |
| 36 | "msg 0x%x failed with PSP error: 0x%x, extended status: 0x%x\n" , |
| 37 | msg, sfs_dev->command_buf->hdr.status, |
| 38 | *(u32 *)sfs_dev->command_buf->buf); |
| 39 | } |
| 40 | |
| 41 | return ret; |
| 42 | } |
| 43 | |
| 44 | static int send_sfs_get_fw_versions(struct sfs_device *sfs_dev) |
| 45 | { |
| 46 | /* |
| 47 | * SFS_GET_FW_VERSIONS command needs the output buffer to be |
| 48 | * initialized to 0xC7 in every byte. |
| 49 | */ |
| 50 | memset(sfs_dev->command_buf->sfs_buffer, 0xc7, PAGE_SIZE); |
| 51 | sfs_dev->command_buf->hdr.payload_size = 2 * PAGE_SIZE; |
| 52 | |
| 53 | return send_sfs_cmd(sfs_dev, msg: PSP_SFS_GET_FW_VERSIONS); |
| 54 | } |
| 55 | |
| 56 | static int send_sfs_update_package(struct sfs_device *sfs_dev, const char *payload_name) |
| 57 | { |
| 58 | char payload_path[PAYLOAD_NAME_SIZE + sizeof("amd/" )]; |
| 59 | const struct firmware *firmware; |
| 60 | unsigned long package_size; |
| 61 | int ret; |
| 62 | |
| 63 | /* Sanitize userspace provided payload name */ |
| 64 | if (!strnchr(payload_name, PAYLOAD_NAME_SIZE, '\0')) |
| 65 | return -EINVAL; |
| 66 | |
| 67 | snprintf(buf: payload_path, size: sizeof(payload_path), fmt: "amd/%s" , payload_name); |
| 68 | |
| 69 | ret = firmware_request_nowarn(fw: &firmware, name: payload_path, device: sfs_dev->dev); |
| 70 | if (ret < 0) { |
| 71 | dev_warn_ratelimited(sfs_dev->dev, "firmware request failed for %s (%d)\n" , |
| 72 | payload_path, ret); |
| 73 | return -ENOENT; |
| 74 | } |
| 75 | |
| 76 | /* |
| 77 | * SFS Update Package command's input buffer contains TEE_EXT_CMD_BUFFER |
| 78 | * followed by the Update Package and it should be 64KB aligned. |
| 79 | */ |
| 80 | package_size = ALIGN(firmware->size + PAGE_SIZE, 0x10000U); |
| 81 | |
| 82 | /* |
| 83 | * SFS command buffer is a pre-allocated 2MB buffer, fail update package |
| 84 | * if SFS payload is larger than the pre-allocated command buffer. |
| 85 | */ |
| 86 | if (package_size > SFS_MAX_PAYLOAD_SIZE) { |
| 87 | dev_warn_ratelimited(sfs_dev->dev, |
| 88 | "SFS payload size %ld larger than maximum supported payload size of %u\n" , |
| 89 | package_size, SFS_MAX_PAYLOAD_SIZE); |
| 90 | release_firmware(fw: firmware); |
| 91 | return -E2BIG; |
| 92 | } |
| 93 | |
| 94 | /* |
| 95 | * Copy firmware data to a HV_Fixed memory region. |
| 96 | */ |
| 97 | memcpy(sfs_dev->command_buf->sfs_buffer, firmware->data, firmware->size); |
| 98 | sfs_dev->command_buf->hdr.payload_size = package_size; |
| 99 | |
| 100 | release_firmware(fw: firmware); |
| 101 | |
| 102 | return send_sfs_cmd(sfs_dev, msg: PSP_SFS_UPDATE); |
| 103 | } |
| 104 | |
| 105 | static long sfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) |
| 106 | { |
| 107 | struct sfs_user_get_fw_versions __user *sfs_get_fw_versions; |
| 108 | struct sfs_user_update_package __user *sfs_update_package; |
| 109 | struct psp_device *psp_master = psp_get_master_device(); |
| 110 | char payload_name[PAYLOAD_NAME_SIZE]; |
| 111 | struct sfs_device *sfs_dev; |
| 112 | int ret = 0; |
| 113 | |
| 114 | if (!psp_master || !psp_master->sfs_data) |
| 115 | return -ENODEV; |
| 116 | |
| 117 | sfs_dev = psp_master->sfs_data; |
| 118 | |
| 119 | guard(mutex)(T: &sfs_ioctl_mutex); |
| 120 | |
| 121 | switch (cmd) { |
| 122 | case SFSIOCFWVERS: |
| 123 | dev_dbg(sfs_dev->dev, "in SFSIOCFWVERS\n" ); |
| 124 | |
| 125 | sfs_get_fw_versions = (struct sfs_user_get_fw_versions __user *)arg; |
| 126 | |
| 127 | ret = send_sfs_get_fw_versions(sfs_dev); |
| 128 | if (ret && ret != -EIO) |
| 129 | return ret; |
| 130 | |
| 131 | /* |
| 132 | * Return SFS status and extended status back to userspace |
| 133 | * if PSP status indicated success or command error. |
| 134 | */ |
| 135 | if (copy_to_user(to: &sfs_get_fw_versions->blob, from: sfs_dev->command_buf->sfs_buffer, |
| 136 | PAGE_SIZE)) |
| 137 | return -EFAULT; |
| 138 | if (copy_to_user(to: &sfs_get_fw_versions->sfs_status, |
| 139 | from: &sfs_dev->command_buf->hdr.status, |
| 140 | n: sizeof(sfs_get_fw_versions->sfs_status))) |
| 141 | return -EFAULT; |
| 142 | if (copy_to_user(to: &sfs_get_fw_versions->sfs_extended_status, |
| 143 | from: &sfs_dev->command_buf->buf, |
| 144 | n: sizeof(sfs_get_fw_versions->sfs_extended_status))) |
| 145 | return -EFAULT; |
| 146 | break; |
| 147 | case SFSIOCUPDATEPKG: |
| 148 | dev_dbg(sfs_dev->dev, "in SFSIOCUPDATEPKG\n" ); |
| 149 | |
| 150 | sfs_update_package = (struct sfs_user_update_package __user *)arg; |
| 151 | |
| 152 | if (copy_from_user(to: payload_name, from: sfs_update_package->payload_name, |
| 153 | PAYLOAD_NAME_SIZE)) |
| 154 | return -EFAULT; |
| 155 | |
| 156 | ret = send_sfs_update_package(sfs_dev, payload_name); |
| 157 | if (ret && ret != -EIO) |
| 158 | return ret; |
| 159 | |
| 160 | /* |
| 161 | * Return SFS status and extended status back to userspace |
| 162 | * if PSP status indicated success or command error. |
| 163 | */ |
| 164 | if (copy_to_user(to: &sfs_update_package->sfs_status, |
| 165 | from: &sfs_dev->command_buf->hdr.status, |
| 166 | n: sizeof(sfs_update_package->sfs_status))) |
| 167 | return -EFAULT; |
| 168 | if (copy_to_user(to: &sfs_update_package->sfs_extended_status, |
| 169 | from: &sfs_dev->command_buf->buf, |
| 170 | n: sizeof(sfs_update_package->sfs_extended_status))) |
| 171 | return -EFAULT; |
| 172 | break; |
| 173 | default: |
| 174 | ret = -EINVAL; |
| 175 | } |
| 176 | |
| 177 | return ret; |
| 178 | } |
| 179 | |
| 180 | static const struct file_operations sfs_fops = { |
| 181 | .owner = THIS_MODULE, |
| 182 | .unlocked_ioctl = sfs_ioctl, |
| 183 | }; |
| 184 | |
| 185 | static void sfs_exit(struct kref *ref) |
| 186 | { |
| 187 | misc_deregister(misc: &misc_dev->misc); |
| 188 | kfree(objp: misc_dev); |
| 189 | misc_dev = NULL; |
| 190 | } |
| 191 | |
| 192 | void sfs_dev_destroy(struct psp_device *psp) |
| 193 | { |
| 194 | struct sfs_device *sfs_dev = psp->sfs_data; |
| 195 | |
| 196 | if (!sfs_dev) |
| 197 | return; |
| 198 | |
| 199 | /* |
| 200 | * Change SFS command buffer back to the default "Write-Back" type. |
| 201 | */ |
| 202 | set_memory_wb(addr: (unsigned long)sfs_dev->command_buf, SFS_NUM_PAGES_CMDBUF); |
| 203 | |
| 204 | snp_free_hv_fixed_pages(page: sfs_dev->page); |
| 205 | |
| 206 | if (sfs_dev->misc) |
| 207 | kref_put(kref: &misc_dev->refcount, release: sfs_exit); |
| 208 | |
| 209 | psp->sfs_data = NULL; |
| 210 | } |
| 211 | |
| 212 | /* Based on sev_misc_init() */ |
| 213 | static int sfs_misc_init(struct sfs_device *sfs) |
| 214 | { |
| 215 | struct device *dev = sfs->dev; |
| 216 | int ret; |
| 217 | |
| 218 | /* |
| 219 | * SFS feature support can be detected on multiple devices but the SFS |
| 220 | * FW commands must be issued on the master. During probe, we do not |
| 221 | * know the master hence we create /dev/sfs on the first device probe. |
| 222 | */ |
| 223 | if (!misc_dev) { |
| 224 | struct miscdevice *misc; |
| 225 | |
| 226 | misc_dev = kzalloc(sizeof(*misc_dev), GFP_KERNEL); |
| 227 | if (!misc_dev) |
| 228 | return -ENOMEM; |
| 229 | |
| 230 | misc = &misc_dev->misc; |
| 231 | misc->minor = MISC_DYNAMIC_MINOR; |
| 232 | misc->name = "sfs" ; |
| 233 | misc->fops = &sfs_fops; |
| 234 | misc->mode = 0600; |
| 235 | |
| 236 | ret = misc_register(misc); |
| 237 | if (ret) |
| 238 | return ret; |
| 239 | |
| 240 | kref_init(kref: &misc_dev->refcount); |
| 241 | } else { |
| 242 | kref_get(kref: &misc_dev->refcount); |
| 243 | } |
| 244 | |
| 245 | sfs->misc = misc_dev; |
| 246 | dev_dbg(dev, "registered SFS device\n" ); |
| 247 | |
| 248 | return 0; |
| 249 | } |
| 250 | |
| 251 | int sfs_dev_init(struct psp_device *psp) |
| 252 | { |
| 253 | struct device *dev = psp->dev; |
| 254 | struct sfs_device *sfs_dev; |
| 255 | struct page *page; |
| 256 | int ret = -ENOMEM; |
| 257 | |
| 258 | sfs_dev = devm_kzalloc(dev, size: sizeof(*sfs_dev), GFP_KERNEL); |
| 259 | if (!sfs_dev) |
| 260 | return -ENOMEM; |
| 261 | |
| 262 | /* |
| 263 | * Pre-allocate 2MB command buffer for all SFS commands using |
| 264 | * SNP HV_Fixed page allocator which also transitions the |
| 265 | * SFS command buffer to HV_Fixed page state if SNP is enabled. |
| 266 | */ |
| 267 | page = snp_alloc_hv_fixed_pages(SFS_NUM_2MB_PAGES_CMDBUF); |
| 268 | if (!page) { |
| 269 | dev_dbg(dev, "Command Buffer HV-Fixed page allocation failed\n" ); |
| 270 | goto cleanup_dev; |
| 271 | } |
| 272 | sfs_dev->page = page; |
| 273 | sfs_dev->command_buf = page_address(page); |
| 274 | |
| 275 | dev_dbg(dev, "Command buffer 0x%px to be marked as HV_Fixed\n" , sfs_dev->command_buf); |
| 276 | |
| 277 | /* |
| 278 | * SFS command buffer must be mapped as non-cacheable. |
| 279 | */ |
| 280 | ret = set_memory_uc(addr: (unsigned long)sfs_dev->command_buf, SFS_NUM_PAGES_CMDBUF); |
| 281 | if (ret) { |
| 282 | dev_dbg(dev, "Set memory uc failed\n" ); |
| 283 | goto cleanup_cmd_buf; |
| 284 | } |
| 285 | |
| 286 | dev_dbg(dev, "Command buffer 0x%px marked uncacheable\n" , sfs_dev->command_buf); |
| 287 | |
| 288 | psp->sfs_data = sfs_dev; |
| 289 | sfs_dev->dev = dev; |
| 290 | sfs_dev->psp = psp; |
| 291 | |
| 292 | ret = sfs_misc_init(sfs: sfs_dev); |
| 293 | if (ret) |
| 294 | goto cleanup_mem_attr; |
| 295 | |
| 296 | dev_notice(sfs_dev->dev, "SFS support is available\n" ); |
| 297 | |
| 298 | return 0; |
| 299 | |
| 300 | cleanup_mem_attr: |
| 301 | set_memory_wb(addr: (unsigned long)sfs_dev->command_buf, SFS_NUM_PAGES_CMDBUF); |
| 302 | |
| 303 | cleanup_cmd_buf: |
| 304 | snp_free_hv_fixed_pages(page); |
| 305 | |
| 306 | cleanup_dev: |
| 307 | psp->sfs_data = NULL; |
| 308 | devm_kfree(dev, p: sfs_dev); |
| 309 | |
| 310 | return ret; |
| 311 | } |
| 312 | |