brintos

brintos / linux-shallow public Read only

0
0
Text · 20.4 KiB · 165b141 Raw
809 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Provides user-space access to the SSAM EC via the /dev/surface/aggregator4 * misc device. Intended for debugging and development.5 *6 * Copyright (C) 2020-2022 Maximilian Luz <luzmaximilian@gmail.com>7 */8 9#include <linux/fs.h>10#include <linux/ioctl.h>11#include <linux/kernel.h>12#include <linux/kfifo.h>13#include <linux/kref.h>14#include <linux/miscdevice.h>15#include <linux/module.h>16#include <linux/platform_device.h>17#include <linux/poll.h>18#include <linux/rwsem.h>19#include <linux/slab.h>20#include <linux/uaccess.h>21#include <linux/vmalloc.h>22 23#include <linux/surface_aggregator/cdev.h>24#include <linux/surface_aggregator/controller.h>25#include <linux/surface_aggregator/serial_hub.h>26 27#define SSAM_CDEV_DEVICE_NAME	"surface_aggregator_cdev"28 29 30/* -- Main structures. ------------------------------------------------------ */31 32enum ssam_cdev_device_state {33	SSAM_CDEV_DEVICE_SHUTDOWN_BIT = BIT(0),34};35 36struct ssam_cdev {37	struct kref kref;38	struct rw_semaphore lock;39 40	struct device *dev;41	struct ssam_controller *ctrl;42	struct miscdevice mdev;43	unsigned long flags;44 45	struct rw_semaphore client_lock;  /* Guards client list. */46	struct list_head client_list;47};48 49struct ssam_cdev_client;50 51struct ssam_cdev_notifier {52	struct ssam_cdev_client *client;53	struct ssam_event_notifier nf;54};55 56struct ssam_cdev_client {57	struct ssam_cdev *cdev;58	struct list_head node;59 60	struct mutex notifier_lock;	/* Guards notifier access for registration */61	struct ssam_cdev_notifier *notifier[SSH_NUM_EVENTS];62 63	struct mutex read_lock;		/* Guards FIFO buffer read access */64	struct mutex write_lock;	/* Guards FIFO buffer write access */65	DECLARE_KFIFO(buffer, u8, 4096);66 67	wait_queue_head_t waitq;68	struct fasync_struct *fasync;69};70 71static void __ssam_cdev_release(struct kref *kref)72{73	kfree(container_of(kref, struct ssam_cdev, kref));74}75 76static struct ssam_cdev *ssam_cdev_get(struct ssam_cdev *cdev)77{78	if (cdev)79		kref_get(&cdev->kref);80 81	return cdev;82}83 84static void ssam_cdev_put(struct ssam_cdev *cdev)85{86	if (cdev)87		kref_put(&cdev->kref, __ssam_cdev_release);88}89 90 91/* -- Notifier handling. ---------------------------------------------------- */92 93static u32 ssam_cdev_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in)94{95	struct ssam_cdev_notifier *cdev_nf = container_of(nf, struct ssam_cdev_notifier, nf);96	struct ssam_cdev_client *client = cdev_nf->client;97	struct ssam_cdev_event event;98	size_t n = struct_size(&event, data, in->length);99 100	/* Translate event. */101	event.target_category = in->target_category;102	event.target_id = in->target_id;103	event.command_id = in->command_id;104	event.instance_id = in->instance_id;105	event.length = in->length;106 107	mutex_lock(&client->write_lock);108 109	/* Make sure we have enough space. */110	if (kfifo_avail(&client->buffer) < n) {111		dev_warn(client->cdev->dev,112			 "buffer full, dropping event (tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n",113			 in->target_category, in->target_id, in->command_id, in->instance_id);114		mutex_unlock(&client->write_lock);115		return 0;116	}117 118	/* Copy event header and payload. */119	kfifo_in(&client->buffer, (const u8 *)&event, struct_size(&event, data, 0));120	kfifo_in(&client->buffer, &in->data[0], in->length);121 122	mutex_unlock(&client->write_lock);123 124	/* Notify waiting readers. */125	kill_fasync(&client->fasync, SIGIO, POLL_IN);126	wake_up_interruptible(&client->waitq);127 128	/*129	 * Don't mark events as handled, this is the job of a proper driver and130	 * not the debugging interface.131	 */132	return 0;133}134 135static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 tc, int priority)136{137	const u16 rqid = ssh_tc_to_rqid(tc);138	const u16 event = ssh_rqid_to_event(rqid);139	struct ssam_cdev_notifier *nf;140	int status;141 142	lockdep_assert_held_read(&client->cdev->lock);143 144	/* Validate notifier target category. */145	if (!ssh_rqid_is_event(rqid))146		return -EINVAL;147 148	mutex_lock(&client->notifier_lock);149 150	/* Check if the notifier has already been registered. */151	if (client->notifier[event]) {152		mutex_unlock(&client->notifier_lock);153		return -EEXIST;154	}155 156	/* Allocate new notifier. */157	nf = kzalloc(sizeof(*nf), GFP_KERNEL);158	if (!nf) {159		mutex_unlock(&client->notifier_lock);160		return -ENOMEM;161	}162 163	/*164	 * Create a dummy notifier with the minimal required fields for165	 * observer registration. Note that we can skip fully specifying event166	 * and registry here as we do not need any matching and use silent167	 * registration, which does not enable the corresponding event.168	 */169	nf->client = client;170	nf->nf.base.fn = ssam_cdev_notifier;171	nf->nf.base.priority = priority;172	nf->nf.event.id.target_category = tc;173	nf->nf.event.mask = 0;	/* Do not do any matching. */174	nf->nf.flags = SSAM_EVENT_NOTIFIER_OBSERVER;175 176	/* Register notifier. */177	status = ssam_notifier_register(client->cdev->ctrl, &nf->nf);178	if (status)179		kfree(nf);180	else181		client->notifier[event] = nf;182 183	mutex_unlock(&client->notifier_lock);184	return status;185}186 187static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 tc)188{189	const u16 rqid = ssh_tc_to_rqid(tc);190	const u16 event = ssh_rqid_to_event(rqid);191	int status;192 193	lockdep_assert_held_read(&client->cdev->lock);194 195	/* Validate notifier target category. */196	if (!ssh_rqid_is_event(rqid))197		return -EINVAL;198 199	mutex_lock(&client->notifier_lock);200 201	/* Check if the notifier is currently registered. */202	if (!client->notifier[event]) {203		mutex_unlock(&client->notifier_lock);204		return -ENOENT;205	}206 207	/* Unregister and free notifier. */208	status = ssam_notifier_unregister(client->cdev->ctrl, &client->notifier[event]->nf);209	kfree(client->notifier[event]);210	client->notifier[event] = NULL;211 212	mutex_unlock(&client->notifier_lock);213	return status;214}215 216static void ssam_cdev_notifier_unregister_all(struct ssam_cdev_client *client)217{218	int i;219 220	down_read(&client->cdev->lock);221 222	/*223	 * This function may be used during shutdown, thus we need to test for224	 * cdev->ctrl instead of the SSAM_CDEV_DEVICE_SHUTDOWN_BIT bit.225	 */226	if (client->cdev->ctrl) {227		for (i = 0; i < SSH_NUM_EVENTS; i++)228			ssam_cdev_notifier_unregister(client, i + 1);229 230	} else {231		int count = 0;232 233		/*234		 * Device has been shut down. Any notifier remaining is a bug,235		 * so warn about that as this would otherwise hardly be236		 * noticeable. Nevertheless, free them as well.237		 */238		mutex_lock(&client->notifier_lock);239		for (i = 0; i < SSH_NUM_EVENTS; i++) {240			count += !!(client->notifier[i]);241			kfree(client->notifier[i]);242			client->notifier[i] = NULL;243		}244		mutex_unlock(&client->notifier_lock);245 246		WARN_ON(count > 0);247	}248 249	up_read(&client->cdev->lock);250}251 252 253/* -- IOCTL functions. ------------------------------------------------------ */254 255static long ssam_cdev_request(struct ssam_cdev_client *client, struct ssam_cdev_request __user *r)256{257	struct ssam_cdev_request rqst;258	struct ssam_request spec = {};259	struct ssam_response rsp = {};260	const void __user *plddata;261	void __user *rspdata;262	int status = 0, ret = 0, tmp;263 264	lockdep_assert_held_read(&client->cdev->lock);265 266	ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r));267	if (ret)268		goto out;269 270	plddata = u64_to_user_ptr(rqst.payload.data);271	rspdata = u64_to_user_ptr(rqst.response.data);272 273	/* Setup basic request fields. */274	spec.target_category = rqst.target_category;275	spec.target_id = rqst.target_id;276	spec.command_id = rqst.command_id;277	spec.instance_id = rqst.instance_id;278	spec.flags = 0;279	spec.length = rqst.payload.length;280	spec.payload = NULL;281 282	if (rqst.flags & SSAM_CDEV_REQUEST_HAS_RESPONSE)283		spec.flags |= SSAM_REQUEST_HAS_RESPONSE;284 285	if (rqst.flags & SSAM_CDEV_REQUEST_UNSEQUENCED)286		spec.flags |= SSAM_REQUEST_UNSEQUENCED;287 288	rsp.capacity = rqst.response.length;289	rsp.length = 0;290	rsp.pointer = NULL;291 292	/* Get request payload from user-space. */293	if (spec.length) {294		if (!plddata) {295			ret = -EINVAL;296			goto out;297		}298 299		/*300		 * Note: spec.length is limited to U16_MAX bytes via struct301		 * ssam_cdev_request. This is slightly larger than the302		 * theoretical maximum (SSH_COMMAND_MAX_PAYLOAD_SIZE) of the303		 * underlying protocol (note that nothing remotely this size304		 * should ever be allocated in any normal case). This size is305		 * validated later in ssam_request_do_sync(), for allocation306		 * the bound imposed by u16 should be enough.307		 */308		spec.payload = kzalloc(spec.length, GFP_KERNEL);309		if (!spec.payload) {310			ret = -ENOMEM;311			goto out;312		}313 314		if (copy_from_user((void *)spec.payload, plddata, spec.length)) {315			ret = -EFAULT;316			goto out;317		}318	}319 320	/* Allocate response buffer. */321	if (rsp.capacity) {322		if (!rspdata) {323			ret = -EINVAL;324			goto out;325		}326 327		/*328		 * Note: rsp.capacity is limited to U16_MAX bytes via struct329		 * ssam_cdev_request. This is slightly larger than the330		 * theoretical maximum (SSH_COMMAND_MAX_PAYLOAD_SIZE) of the331		 * underlying protocol (note that nothing remotely this size332		 * should ever be allocated in any normal case). In later use,333		 * this capacity does not have to be strictly bounded, as it334		 * is only used as an output buffer to be written to. For335		 * allocation the bound imposed by u16 should be enough.336		 */337		rsp.pointer = kzalloc(rsp.capacity, GFP_KERNEL);338		if (!rsp.pointer) {339			ret = -ENOMEM;340			goto out;341		}342	}343 344	/* Perform request. */345	status = ssam_request_do_sync(client->cdev->ctrl, &spec, &rsp);346	if (status)347		goto out;348 349	/* Copy response to user-space. */350	if (rsp.length && copy_to_user(rspdata, rsp.pointer, rsp.length))351		ret = -EFAULT;352 353out:354	/* Always try to set response-length and status. */355	tmp = put_user(rsp.length, &r->response.length);356	if (tmp)357		ret = tmp;358 359	tmp = put_user(status, &r->status);360	if (tmp)361		ret = tmp;362 363	/* Cleanup. */364	kfree(spec.payload);365	kfree(rsp.pointer);366 367	return ret;368}369 370static long ssam_cdev_notif_register(struct ssam_cdev_client *client,371				     const struct ssam_cdev_notifier_desc __user *d)372{373	struct ssam_cdev_notifier_desc desc;374	long ret;375 376	lockdep_assert_held_read(&client->cdev->lock);377 378	ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));379	if (ret)380		return ret;381 382	return ssam_cdev_notifier_register(client, desc.target_category, desc.priority);383}384 385static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client,386				       const struct ssam_cdev_notifier_desc __user *d)387{388	struct ssam_cdev_notifier_desc desc;389	long ret;390 391	lockdep_assert_held_read(&client->cdev->lock);392 393	ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));394	if (ret)395		return ret;396 397	return ssam_cdev_notifier_unregister(client, desc.target_category);398}399 400static long ssam_cdev_event_enable(struct ssam_cdev_client *client,401				   const struct ssam_cdev_event_desc __user *d)402{403	struct ssam_cdev_event_desc desc;404	struct ssam_event_registry reg;405	struct ssam_event_id id;406	long ret;407 408	lockdep_assert_held_read(&client->cdev->lock);409 410	/* Read descriptor from user-space. */411	ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));412	if (ret)413		return ret;414 415	/* Translate descriptor. */416	reg.target_category = desc.reg.target_category;417	reg.target_id = desc.reg.target_id;418	reg.cid_enable = desc.reg.cid_enable;419	reg.cid_disable = desc.reg.cid_disable;420 421	id.target_category = desc.id.target_category;422	id.instance = desc.id.instance;423 424	/* Disable event. */425	return ssam_controller_event_enable(client->cdev->ctrl, reg, id, desc.flags);426}427 428static long ssam_cdev_event_disable(struct ssam_cdev_client *client,429				    const struct ssam_cdev_event_desc __user *d)430{431	struct ssam_cdev_event_desc desc;432	struct ssam_event_registry reg;433	struct ssam_event_id id;434	long ret;435 436	lockdep_assert_held_read(&client->cdev->lock);437 438	/* Read descriptor from user-space. */439	ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));440	if (ret)441		return ret;442 443	/* Translate descriptor. */444	reg.target_category = desc.reg.target_category;445	reg.target_id = desc.reg.target_id;446	reg.cid_enable = desc.reg.cid_enable;447	reg.cid_disable = desc.reg.cid_disable;448 449	id.target_category = desc.id.target_category;450	id.instance = desc.id.instance;451 452	/* Disable event. */453	return ssam_controller_event_disable(client->cdev->ctrl, reg, id, desc.flags);454}455 456 457/* -- File operations. ------------------------------------------------------ */458 459static int ssam_cdev_device_open(struct inode *inode, struct file *filp)460{461	struct miscdevice *mdev = filp->private_data;462	struct ssam_cdev_client *client;463	struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev);464 465	/* Initialize client */466	client = vzalloc(sizeof(*client));467	if (!client)468		return -ENOMEM;469 470	client->cdev = ssam_cdev_get(cdev);471 472	INIT_LIST_HEAD(&client->node);473 474	mutex_init(&client->notifier_lock);475 476	mutex_init(&client->read_lock);477	mutex_init(&client->write_lock);478	INIT_KFIFO(client->buffer);479	init_waitqueue_head(&client->waitq);480 481	filp->private_data = client;482 483	/* Attach client. */484	down_write(&cdev->client_lock);485 486	if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) {487		up_write(&cdev->client_lock);488		mutex_destroy(&client->write_lock);489		mutex_destroy(&client->read_lock);490		mutex_destroy(&client->notifier_lock);491		ssam_cdev_put(client->cdev);492		vfree(client);493		return -ENODEV;494	}495	list_add_tail(&client->node, &cdev->client_list);496 497	up_write(&cdev->client_lock);498 499	stream_open(inode, filp);500	return 0;501}502 503static int ssam_cdev_device_release(struct inode *inode, struct file *filp)504{505	struct ssam_cdev_client *client = filp->private_data;506 507	/* Force-unregister all remaining notifiers of this client. */508	ssam_cdev_notifier_unregister_all(client);509 510	/* Detach client. */511	down_write(&client->cdev->client_lock);512	list_del(&client->node);513	up_write(&client->cdev->client_lock);514 515	/* Free client. */516	mutex_destroy(&client->write_lock);517	mutex_destroy(&client->read_lock);518 519	mutex_destroy(&client->notifier_lock);520 521	ssam_cdev_put(client->cdev);522	vfree(client);523 524	return 0;525}526 527static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned int cmd,528				     unsigned long arg)529{530	lockdep_assert_held_read(&client->cdev->lock);531 532	switch (cmd) {533	case SSAM_CDEV_REQUEST:534		return ssam_cdev_request(client, (struct ssam_cdev_request __user *)arg);535 536	case SSAM_CDEV_NOTIF_REGISTER:537		return ssam_cdev_notif_register(client,538						(struct ssam_cdev_notifier_desc __user *)arg);539 540	case SSAM_CDEV_NOTIF_UNREGISTER:541		return ssam_cdev_notif_unregister(client,542						  (struct ssam_cdev_notifier_desc __user *)arg);543 544	case SSAM_CDEV_EVENT_ENABLE:545		return ssam_cdev_event_enable(client, (struct ssam_cdev_event_desc __user *)arg);546 547	case SSAM_CDEV_EVENT_DISABLE:548		return ssam_cdev_event_disable(client, (struct ssam_cdev_event_desc __user *)arg);549 550	default:551		return -ENOTTY;552	}553}554 555static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, unsigned long arg)556{557	struct ssam_cdev_client *client = file->private_data;558	long status;559 560	/* Ensure that controller is valid for as long as we need it. */561	if (down_read_killable(&client->cdev->lock))562		return -ERESTARTSYS;563 564	if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags)) {565		up_read(&client->cdev->lock);566		return -ENODEV;567	}568 569	status = __ssam_cdev_device_ioctl(client, cmd, arg);570 571	up_read(&client->cdev->lock);572	return status;573}574 575static ssize_t ssam_cdev_read(struct file *file, char __user *buf, size_t count, loff_t *offs)576{577	struct ssam_cdev_client *client = file->private_data;578	struct ssam_cdev *cdev = client->cdev;579	unsigned int copied;580	int status = 0;581 582	if (down_read_killable(&cdev->lock))583		return -ERESTARTSYS;584 585	/* Make sure we're not shut down. */586	if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) {587		up_read(&cdev->lock);588		return -ENODEV;589	}590 591	do {592		/* Check availability, wait if necessary. */593		if (kfifo_is_empty(&client->buffer)) {594			up_read(&cdev->lock);595 596			if (file->f_flags & O_NONBLOCK)597				return -EAGAIN;598 599			status = wait_event_interruptible(client->waitq,600							  !kfifo_is_empty(&client->buffer) ||601							  test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT,602								   &cdev->flags));603			if (status < 0)604				return status;605 606			if (down_read_killable(&cdev->lock))607				return -ERESTARTSYS;608 609			/* Need to check that we're not shut down again. */610			if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) {611				up_read(&cdev->lock);612				return -ENODEV;613			}614		}615 616		/* Try to read from FIFO. */617		if (mutex_lock_interruptible(&client->read_lock)) {618			up_read(&cdev->lock);619			return -ERESTARTSYS;620		}621 622		status = kfifo_to_user(&client->buffer, buf, count, &copied);623		mutex_unlock(&client->read_lock);624 625		if (status < 0) {626			up_read(&cdev->lock);627			return status;628		}629 630		/* We might not have gotten anything, check this here. */631		if (copied == 0 && (file->f_flags & O_NONBLOCK)) {632			up_read(&cdev->lock);633			return -EAGAIN;634		}635	} while (copied == 0);636 637	up_read(&cdev->lock);638	return copied;639}640 641static __poll_t ssam_cdev_poll(struct file *file, struct poll_table_struct *pt)642{643	struct ssam_cdev_client *client = file->private_data;644	__poll_t events = 0;645 646	if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags))647		return EPOLLHUP | EPOLLERR;648 649	poll_wait(file, &client->waitq, pt);650 651	if (!kfifo_is_empty(&client->buffer))652		events |= EPOLLIN | EPOLLRDNORM;653 654	return events;655}656 657static int ssam_cdev_fasync(int fd, struct file *file, int on)658{659	struct ssam_cdev_client *client = file->private_data;660 661	return fasync_helper(fd, file, on, &client->fasync);662}663 664static const struct file_operations ssam_controller_fops = {665	.owner          = THIS_MODULE,666	.open           = ssam_cdev_device_open,667	.release        = ssam_cdev_device_release,668	.read           = ssam_cdev_read,669	.poll           = ssam_cdev_poll,670	.fasync         = ssam_cdev_fasync,671	.unlocked_ioctl = ssam_cdev_device_ioctl,672	.compat_ioctl   = ssam_cdev_device_ioctl,673};674 675 676/* -- Device and driver setup ----------------------------------------------- */677 678static int ssam_dbg_device_probe(struct platform_device *pdev)679{680	struct ssam_controller *ctrl;681	struct ssam_cdev *cdev;682	int status;683 684	ctrl = ssam_client_bind(&pdev->dev);685	if (IS_ERR(ctrl))686		return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);687 688	cdev = kzalloc(sizeof(*cdev), GFP_KERNEL);689	if (!cdev)690		return -ENOMEM;691 692	kref_init(&cdev->kref);693	init_rwsem(&cdev->lock);694	cdev->ctrl = ctrl;695	cdev->dev = &pdev->dev;696 697	cdev->mdev.parent   = &pdev->dev;698	cdev->mdev.minor    = MISC_DYNAMIC_MINOR;699	cdev->mdev.name     = "surface_aggregator";700	cdev->mdev.nodename = "surface/aggregator";701	cdev->mdev.fops     = &ssam_controller_fops;702 703	init_rwsem(&cdev->client_lock);704	INIT_LIST_HEAD(&cdev->client_list);705 706	status = misc_register(&cdev->mdev);707	if (status) {708		kfree(cdev);709		return status;710	}711 712	platform_set_drvdata(pdev, cdev);713	return 0;714}715 716static void ssam_dbg_device_remove(struct platform_device *pdev)717{718	struct ssam_cdev *cdev = platform_get_drvdata(pdev);719	struct ssam_cdev_client *client;720 721	/*722	 * Mark device as shut-down. Prevent new clients from being added and723	 * new operations from being executed.724	 */725	set_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags);726 727	down_write(&cdev->client_lock);728 729	/* Remove all notifiers registered by us. */730	list_for_each_entry(client, &cdev->client_list, node) {731		ssam_cdev_notifier_unregister_all(client);732	}733 734	/* Wake up async clients. */735	list_for_each_entry(client, &cdev->client_list, node) {736		kill_fasync(&client->fasync, SIGIO, POLL_HUP);737	}738 739	/* Wake up blocking clients. */740	list_for_each_entry(client, &cdev->client_list, node) {741		wake_up_interruptible(&client->waitq);742	}743 744	up_write(&cdev->client_lock);745 746	/*747	 * The controller is only guaranteed to be valid for as long as the748	 * driver is bound. Remove controller so that any lingering open files749	 * cannot access it any more after we're gone.750	 */751	down_write(&cdev->lock);752	cdev->ctrl = NULL;753	cdev->dev = NULL;754	up_write(&cdev->lock);755 756	misc_deregister(&cdev->mdev);757 758	ssam_cdev_put(cdev);759}760 761static struct platform_device *ssam_cdev_device;762 763static struct platform_driver ssam_cdev_driver = {764	.probe = ssam_dbg_device_probe,765	.remove_new = ssam_dbg_device_remove,766	.driver = {767		.name = SSAM_CDEV_DEVICE_NAME,768		.probe_type = PROBE_PREFER_ASYNCHRONOUS,769	},770};771 772static int __init ssam_debug_init(void)773{774	int status;775 776	ssam_cdev_device = platform_device_alloc(SSAM_CDEV_DEVICE_NAME,777						 PLATFORM_DEVID_NONE);778	if (!ssam_cdev_device)779		return -ENOMEM;780 781	status = platform_device_add(ssam_cdev_device);782	if (status)783		goto err_device;784 785	status = platform_driver_register(&ssam_cdev_driver);786	if (status)787		goto err_driver;788 789	return 0;790 791err_driver:792	platform_device_del(ssam_cdev_device);793err_device:794	platform_device_put(ssam_cdev_device);795	return status;796}797module_init(ssam_debug_init);798 799static void __exit ssam_debug_exit(void)800{801	platform_driver_unregister(&ssam_cdev_driver);802	platform_device_unregister(ssam_cdev_device);803}804module_exit(ssam_debug_exit);805 806MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");807MODULE_DESCRIPTION("User-space interface for Surface System Aggregator Module");808MODULE_LICENSE("GPL");809