// 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;
}