544 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			544 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// 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 <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("%s: update property (%s, length: %i, value: %#x)\n",
 | 
						|
		  dn->name, 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, drc_index, 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 */
 | 
						|
					drc_index = *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 long device_compat_ioctl(struct file *file, unsigned int cmd,
 | 
						|
				unsigned long arg)
 | 
						|
{
 | 
						|
	return device_ioctl(file, cmd, arg);
 | 
						|
}
 | 
						|
 | 
						|
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	= device_compat_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;
 | 
						|
}
 |