// SPDX-License-Identifier: GPL-2.0 #include <linux/kernel.h> #include <linux/fs.h> #include <linux/semaphore.h> #include <linux/slab.h> #include <linux/uaccess.h> #include <linux/of.h> #include <asm/rtas.h> #include "cxl.h" #include "hcalls.h" #define DOWNLOAD_IMAGE 1 #define VALIDATE_IMAGE 2 struct ai_header { u16 version; u8 reserved0[6]; u16 vendor; u16 device; u16 subsystem_vendor; u16 subsystem; u64 image_offset; u64 image_length; u8 reserved1[96]; }; static struct semaphore sem; static unsigned long *buffer[CXL_AI_MAX_ENTRIES]; static struct sg_list *le; static u64 continue_token; static unsigned int transfer; struct update_props_workarea { __be32 phandle; __be32 state; __be64 reserved; __be32 nprops; } __packed; struct update_nodes_workarea { __be32 state; __be64 unit_address; __be32 reserved; } __packed; #define DEVICE_SCOPE 3 #define NODE_ACTION_MASK 0xff000000 #define NODE_COUNT_MASK 0x00ffffff #define OPCODE_DELETE 0x01000000 #define OPCODE_UPDATE 0x02000000 #define OPCODE_ADD 0x03000000 static int rcall(int token, char *buf, s32 scope) { int rc; spin_lock(&rtas_data_buf_lock); memcpy(rtas_data_buf, buf, RTAS_DATA_BUF_SIZE); rc = rtas_call(token, 2, 1, NULL, rtas_data_buf, scope); memcpy(buf, rtas_data_buf, RTAS_DATA_BUF_SIZE); spin_unlock(&rtas_data_buf_lock); return rc; } static int update_property(struct device_node *dn, const char *name, u32 vd, char *value) { struct property *new_prop; u32 *val; int rc; new_prop = kzalloc(sizeof(*new_prop), GFP_KERNEL); if (!new_prop) return -ENOMEM; new_prop->name = kstrdup(name, GFP_KERNEL); if (!new_prop->name) { kfree(new_prop); return -ENOMEM; } new_prop->length = vd; new_prop->value = kzalloc(new_prop->length, GFP_KERNEL); if (!new_prop->value) { kfree(new_prop->name); kfree(new_prop); return -ENOMEM; } memcpy(new_prop->value, value, vd); val = (u32 *)new_prop->value; rc = cxl_update_properties(dn, new_prop); pr_devel("%pOFn: update property (%s, length: %i, value: %#x)\n", dn, name, vd, be32_to_cpu(*val)); if (rc) { kfree(new_prop->name); kfree(new_prop->value); kfree(new_prop); } return rc; } static int update_node(__be32 phandle, s32 scope) { struct update_props_workarea *upwa; struct device_node *dn; int i, rc, ret; char *prop_data; char *buf; int token; u32 nprops; u32 vd; token = rtas_token("ibm,update-properties"); if (token == RTAS_UNKNOWN_SERVICE) return -EINVAL; buf = kzalloc(RTAS_DATA_BUF_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; dn = of_find_node_by_phandle(be32_to_cpu(phandle)); if (!dn) { kfree(buf); return -ENOENT; } upwa = (struct update_props_workarea *)&buf[0]; upwa->phandle = phandle; do { rc = rcall(token, buf, scope); if (rc < 0) break; prop_data = buf + sizeof(*upwa); nprops = be32_to_cpu(upwa->nprops); if (*prop_data == 0) { prop_data++; vd = be32_to_cpu(*(__be32 *)prop_data); prop_data += vd + sizeof(vd); nprops--; } for (i = 0; i < nprops; i++) { char *prop_name; prop_name = prop_data; prop_data += strlen(prop_name) + 1; vd = be32_to_cpu(*(__be32 *)prop_data); prop_data += sizeof(vd); if ((vd != 0x00000000) && (vd != 0x80000000)) { ret = update_property(dn, prop_name, vd, prop_data); if (ret) pr_err("cxl: Could not update property %s - %i\n", prop_name, ret); prop_data += vd; } } } while (rc == 1); of_node_put(dn); kfree(buf); return rc; } static int update_devicetree(struct cxl *adapter, s32 scope) { struct update_nodes_workarea *unwa; u32 action, node_count; int token, rc, i; __be32 *data, phandle; char *buf; token = rtas_token("ibm,update-nodes"); if (token == RTAS_UNKNOWN_SERVICE) return -EINVAL; buf = kzalloc(RTAS_DATA_BUF_SIZE, GFP_KERNEL); if (!buf) return -ENOMEM; unwa = (struct update_nodes_workarea *)&buf[0]; unwa->unit_address = cpu_to_be64(adapter->guest->handle); do { rc = rcall(token, buf, scope); if (rc && rc != 1) break; data = (__be32 *)buf + 4; while (be32_to_cpu(*data) & NODE_ACTION_MASK) { action = be32_to_cpu(*data) & NODE_ACTION_MASK; node_count = be32_to_cpu(*data) & NODE_COUNT_MASK; pr_devel("device reconfiguration - action: %#x, nodes: %#x\n", action, node_count); data++; for (i = 0; i < node_count; i++) { phandle = *data++; switch (action) { case OPCODE_DELETE: /* nothing to do */ break; case OPCODE_UPDATE: update_node(phandle, scope); break; case OPCODE_ADD: /* nothing to do, just move pointer */ data++; break; } } } } while (rc == 1); kfree(buf); return 0; } static int handle_image(struct cxl *adapter, int operation, long (*fct)(u64, u64, u64, u64 *), struct cxl_adapter_image *ai) { size_t mod, s_copy, len_chunk = 0; struct ai_header *header = NULL; unsigned int entries = 0, i; void *dest, *from; int rc = 0, need_header; /* base adapter image header */ need_header = (ai->flags & CXL_AI_NEED_HEADER); if (need_header) { header = kzalloc(sizeof(struct ai_header), GFP_KERNEL); if (!header) return -ENOMEM; header->version = cpu_to_be16(1); header->vendor = cpu_to_be16(adapter->guest->vendor); header->device = cpu_to_be16(adapter->guest->device); header->subsystem_vendor = cpu_to_be16(adapter->guest->subsystem_vendor); header->subsystem = cpu_to_be16(adapter->guest->subsystem); header->image_offset = cpu_to_be64(CXL_AI_HEADER_SIZE); header->image_length = cpu_to_be64(ai->len_image); } /* number of entries in the list */ len_chunk = ai->len_data; if (need_header) len_chunk += CXL_AI_HEADER_SIZE; entries = len_chunk / CXL_AI_BUFFER_SIZE; mod = len_chunk % CXL_AI_BUFFER_SIZE; if (mod) entries++; if (entries > CXL_AI_MAX_ENTRIES) { rc = -EINVAL; goto err; } /* < -- MAX_CHUNK_SIZE = 4096 * 256 = 1048576 bytes --> * chunk 0 ---------------------------------------------------- * | header | data | * ---------------------------------------------------- * chunk 1 ---------------------------------------------------- * | data | * ---------------------------------------------------- * .... * chunk n ---------------------------------------------------- * | data | * ---------------------------------------------------- */ from = (void *) ai->data; for (i = 0; i < entries; i++) { dest = buffer[i]; s_copy = CXL_AI_BUFFER_SIZE; if ((need_header) && (i == 0)) { /* add adapter image header */ memcpy(buffer[i], header, sizeof(struct ai_header)); s_copy = CXL_AI_BUFFER_SIZE - CXL_AI_HEADER_SIZE; dest += CXL_AI_HEADER_SIZE; /* image offset */ } if ((i == (entries - 1)) && mod) s_copy = mod; /* copy data */ if (copy_from_user(dest, from, s_copy)) goto err; /* fill in the list */ le[i].phys_addr = cpu_to_be64(virt_to_phys(buffer[i])); le[i].len = cpu_to_be64(CXL_AI_BUFFER_SIZE); if ((i == (entries - 1)) && mod) le[i].len = cpu_to_be64(mod); from += s_copy; } pr_devel("%s (op: %i, need header: %i, entries: %i, token: %#llx)\n", __func__, operation, need_header, entries, continue_token); /* * download/validate the adapter image to the coherent * platform facility */ rc = fct(adapter->guest->handle, virt_to_phys(le), entries, &continue_token); if (rc == 0) /* success of download/validation operation */ continue_token = 0; err: kfree(header); return rc; } static int transfer_image(struct cxl *adapter, int operation, struct cxl_adapter_image *ai) { int rc = 0; int afu; switch (operation) { case DOWNLOAD_IMAGE: rc = handle_image(adapter, operation, &cxl_h_download_adapter_image, ai); if (rc < 0) { pr_devel("resetting adapter\n"); cxl_h_reset_adapter(adapter->guest->handle); } return rc; case VALIDATE_IMAGE: rc = handle_image(adapter, operation, &cxl_h_validate_adapter_image, ai); if (rc < 0) { pr_devel("resetting adapter\n"); cxl_h_reset_adapter(adapter->guest->handle); return rc; } if (rc == 0) { pr_devel("remove current afu\n"); for (afu = 0; afu < adapter->slices; afu++) cxl_guest_remove_afu(adapter->afu[afu]); pr_devel("resetting adapter\n"); cxl_h_reset_adapter(adapter->guest->handle); /* The entire image has now been * downloaded and the validation has * been successfully performed. * After that, the partition should call * ibm,update-nodes and * ibm,update-properties to receive the * current configuration */ rc = update_devicetree(adapter, DEVICE_SCOPE); transfer = 1; } return rc; } return -EINVAL; } static long ioctl_transfer_image(struct cxl *adapter, int operation, struct cxl_adapter_image __user *uai) { struct cxl_adapter_image ai; pr_devel("%s\n", __func__); if (copy_from_user(&ai, uai, sizeof(struct cxl_adapter_image))) return -EFAULT; /* * Make sure reserved fields and bits are set to 0 */ if (ai.reserved1 || ai.reserved2 || ai.reserved3 || ai.reserved4 || (ai.flags & ~CXL_AI_ALL)) return -EINVAL; return transfer_image(adapter, operation, &ai); } static int device_open(struct inode *inode, struct file *file) { int adapter_num = CXL_DEVT_ADAPTER(inode->i_rdev); struct cxl *adapter; int rc = 0, i; pr_devel("in %s\n", __func__); BUG_ON(sizeof(struct ai_header) != CXL_AI_HEADER_SIZE); /* Allows one process to open the device by using a semaphore */ if (down_interruptible(&sem) != 0) return -EPERM; if (!(adapter = get_cxl_adapter(adapter_num))) { rc = -ENODEV; goto err_unlock; } file->private_data = adapter; continue_token = 0; transfer = 0; for (i = 0; i < CXL_AI_MAX_ENTRIES; i++) buffer[i] = NULL; /* aligned buffer containing list entries which describes up to * 1 megabyte of data (256 entries of 4096 bytes each) * Logical real address of buffer 0 - Buffer 0 length in bytes * Logical real address of buffer 1 - Buffer 1 length in bytes * Logical real address of buffer 2 - Buffer 2 length in bytes * .... * .... * Logical real address of buffer N - Buffer N length in bytes */ le = (struct sg_list *)get_zeroed_page(GFP_KERNEL); if (!le) { rc = -ENOMEM; goto err; } for (i = 0; i < CXL_AI_MAX_ENTRIES; i++) { buffer[i] = (unsigned long *)get_zeroed_page(GFP_KERNEL); if (!buffer[i]) { rc = -ENOMEM; goto err1; } } return 0; err1: for (i = 0; i < CXL_AI_MAX_ENTRIES; i++) { if (buffer[i]) free_page((unsigned long) buffer[i]); } if (le) free_page((unsigned long) le); err: put_device(&adapter->dev); err_unlock: up(&sem); return rc; } static long device_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct cxl *adapter = file->private_data; pr_devel("in %s\n", __func__); if (cmd == CXL_IOCTL_DOWNLOAD_IMAGE) return ioctl_transfer_image(adapter, DOWNLOAD_IMAGE, (struct cxl_adapter_image __user *)arg); else if (cmd == CXL_IOCTL_VALIDATE_IMAGE) return ioctl_transfer_image(adapter, VALIDATE_IMAGE, (struct cxl_adapter_image __user *)arg); else return -EINVAL; } static int device_close(struct inode *inode, struct file *file) { struct cxl *adapter = file->private_data; int i; pr_devel("in %s\n", __func__); for (i = 0; i < CXL_AI_MAX_ENTRIES; i++) { if (buffer[i]) free_page((unsigned long) buffer[i]); } if (le) free_page((unsigned long) le); up(&sem); put_device(&adapter->dev); continue_token = 0; /* reload the module */ if (transfer) cxl_guest_reload_module(adapter); else { pr_devel("resetting adapter\n"); cxl_h_reset_adapter(adapter->guest->handle); } transfer = 0; return 0; } static const struct file_operations fops = { .owner = THIS_MODULE, .open = device_open, .unlocked_ioctl = device_ioctl, .compat_ioctl = compat_ptr_ioctl, .release = device_close, }; void cxl_guest_remove_chardev(struct cxl *adapter) { cdev_del(&adapter->guest->cdev); } int cxl_guest_add_chardev(struct cxl *adapter) { dev_t devt; int rc; devt = MKDEV(MAJOR(cxl_get_dev()), CXL_CARD_MINOR(adapter)); cdev_init(&adapter->guest->cdev, &fops); if ((rc = cdev_add(&adapter->guest->cdev, devt, 1))) { dev_err(&adapter->dev, "Unable to add chardev on adapter (card%i): %i\n", adapter->adapter_num, rc); goto err; } adapter->dev.devt = devt; sema_init(&sem, 1); err: return rc; }