572 lines · 13.9 KB · C
RawDownload
/* * Xen para-virtual input device * * Copyright (C) 2005 Anthony Liguori <aliguori@us.ibm.com> * Copyright (C) 2006-2008 Red Hat, Inc., Markus Armbruster <armbru@redhat.com> * *  Based on linux/drivers/input/mouse/sermouse.c * *  This file is subject to the terms and conditions of the GNU General Public *  License. See the file COPYING in the main directory of this archive for *  more details. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/kernel.h>#include <linux/errno.h>#include <linux/module.h>#include <linux/input.h>#include <linux/input/mt.h>#include <linux/slab.h> #include <asm/xen/hypervisor.h> #include <xen/xen.h>#include <xen/events.h>#include <xen/page.h>#include <xen/grant_table.h>#include <xen/interface/grant_table.h>#include <xen/interface/io/fbif.h>#include <xen/interface/io/kbdif.h>#include <xen/xenbus.h>#include <xen/platform_pci.h> struct xenkbd_info {	struct input_dev *kbd;	struct input_dev *ptr;	struct input_dev *mtouch;	struct xenkbd_page *page;	int gref;	int irq;	struct xenbus_device *xbdev;	char phys[32];	/* current MT slot/contact ID we are injecting events in */	int mtouch_cur_contact_id;}; enum { KPARAM_X, KPARAM_Y, KPARAM_CNT };static int ptr_size[KPARAM_CNT] = { XENFB_WIDTH, XENFB_HEIGHT };module_param_array(ptr_size, int, NULL, 0444);MODULE_PARM_DESC(ptr_size,	"Pointing device width, height in pixels (default 800,600)"); static void xenkbd_remove(struct xenbus_device *);static int xenkbd_connect_backend(struct xenbus_device *, struct xenkbd_info *);static void xenkbd_disconnect_backend(struct xenkbd_info *); /* * Note: if you need to send out events, see xenfb_do_update() for how * to do that. */ static void xenkbd_handle_motion_event(struct xenkbd_info *info,				       struct xenkbd_motion *motion){	if (unlikely(!info->ptr))		return; 	input_report_rel(info->ptr, REL_X, motion->rel_x);	input_report_rel(info->ptr, REL_Y, motion->rel_y);	if (motion->rel_z)		input_report_rel(info->ptr, REL_WHEEL, -motion->rel_z);	input_sync(info->ptr);} static void xenkbd_handle_position_event(struct xenkbd_info *info,					 struct xenkbd_position *pos){	if (unlikely(!info->ptr))		return; 	input_report_abs(info->ptr, ABS_X, pos->abs_x);	input_report_abs(info->ptr, ABS_Y, pos->abs_y);	if (pos->rel_z)		input_report_rel(info->ptr, REL_WHEEL, -pos->rel_z);	input_sync(info->ptr);} static void xenkbd_handle_key_event(struct xenkbd_info *info,				    struct xenkbd_key *key){	struct input_dev *dev;	int value = key->pressed; 	if (test_bit(key->keycode, info->ptr->keybit)) {		dev = info->ptr;	} else if (test_bit(key->keycode, info->kbd->keybit)) {		dev = info->kbd;		if (key->pressed && test_bit(key->keycode, info->kbd->key))			value = 2; /* Mark as autorepeat */	} else {		pr_warn("unhandled keycode 0x%x\n", key->keycode);		return;	} 	if (unlikely(!dev))		return; 	input_event(dev, EV_KEY, key->keycode, value);	input_sync(dev);} static void xenkbd_handle_mt_event(struct xenkbd_info *info,				   struct xenkbd_mtouch *mtouch){	if (unlikely(!info->mtouch))		return; 	if (mtouch->contact_id != info->mtouch_cur_contact_id) {		info->mtouch_cur_contact_id = mtouch->contact_id;		input_mt_slot(info->mtouch, mtouch->contact_id);	} 	switch (mtouch->event_type) {	case XENKBD_MT_EV_DOWN:		input_mt_report_slot_state(info->mtouch, MT_TOOL_FINGER, true);		fallthrough; 	case XENKBD_MT_EV_MOTION:		input_report_abs(info->mtouch, ABS_MT_POSITION_X,				 mtouch->u.pos.abs_x);		input_report_abs(info->mtouch, ABS_MT_POSITION_Y,				 mtouch->u.pos.abs_y);		break; 	case XENKBD_MT_EV_SHAPE:		input_report_abs(info->mtouch, ABS_MT_TOUCH_MAJOR,				 mtouch->u.shape.major);		input_report_abs(info->mtouch, ABS_MT_TOUCH_MINOR,				 mtouch->u.shape.minor);		break; 	case XENKBD_MT_EV_ORIENT:		input_report_abs(info->mtouch, ABS_MT_ORIENTATION,				 mtouch->u.orientation);		break; 	case XENKBD_MT_EV_UP:		input_mt_report_slot_inactive(info->mtouch);		break; 	case XENKBD_MT_EV_SYN:		input_mt_sync_frame(info->mtouch);		input_sync(info->mtouch);		break;	}} static void xenkbd_handle_event(struct xenkbd_info *info,				union xenkbd_in_event *event){	switch (event->type) {	case XENKBD_TYPE_MOTION:		xenkbd_handle_motion_event(info, &event->motion);		break; 	case XENKBD_TYPE_KEY:		xenkbd_handle_key_event(info, &event->key);		break; 	case XENKBD_TYPE_POS:		xenkbd_handle_position_event(info, &event->pos);		break; 	case XENKBD_TYPE_MTOUCH:		xenkbd_handle_mt_event(info, &event->mtouch);		break;	}} static irqreturn_t input_handler(int rq, void *dev_id){	struct xenkbd_info *info = dev_id;	struct xenkbd_page *page = info->page;	__u32 cons, prod; 	prod = page->in_prod;	if (prod == page->in_cons)		return IRQ_HANDLED;	rmb();			/* ensure we see ring contents up to prod */	for (cons = page->in_cons; cons != prod; cons++)		xenkbd_handle_event(info, &XENKBD_IN_RING_REF(page, cons));	mb();			/* ensure we got ring contents */	page->in_cons = cons;	notify_remote_via_irq(info->irq); 	return IRQ_HANDLED;} static int xenkbd_probe(struct xenbus_device *dev,				  const struct xenbus_device_id *id){	int ret, i;	bool with_mtouch, with_kbd, with_ptr;	struct xenkbd_info *info;	struct input_dev *kbd, *ptr, *mtouch; 	info = kzalloc_obj(*info);	if (!info) {		xenbus_dev_fatal(dev, -ENOMEM, "allocating info structure");		return -ENOMEM;	}	dev_set_drvdata(&dev->dev, info);	info->xbdev = dev;	info->irq = -1;	info->gref = -1;	snprintf(info->phys, sizeof(info->phys), "xenbus/%s", dev->nodename); 	info->page = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO);	if (!info->page)		goto error_nomem; 	/*	 * The below are reverse logic, e.g. if the feature is set, then	 * do not expose the corresponding virtual device.	 */	with_kbd = !xenbus_read_unsigned(dev->otherend,					 XENKBD_FIELD_FEAT_DSBL_KEYBRD, 0); 	with_ptr = !xenbus_read_unsigned(dev->otherend,					 XENKBD_FIELD_FEAT_DSBL_POINTER, 0); 	/* Direct logic: if set, then create multi-touch device. */	with_mtouch = xenbus_read_unsigned(dev->otherend,					   XENKBD_FIELD_FEAT_MTOUCH, 0);	if (with_mtouch) {		ret = xenbus_write(XBT_NIL, dev->nodename,				   XENKBD_FIELD_REQ_MTOUCH, "1");		if (ret) {			pr_warn("xenkbd: can't request multi-touch");			with_mtouch = 0;		}	} 	/* keyboard */	if (with_kbd) {		kbd = input_allocate_device();		if (!kbd)			goto error_nomem;		kbd->name = "Xen Virtual Keyboard";		kbd->phys = info->phys;		kbd->id.bustype = BUS_PCI;		kbd->id.vendor = 0x5853;		kbd->id.product = 0xffff; 		__set_bit(EV_KEY, kbd->evbit);		for (i = KEY_ESC; i < KEY_UNKNOWN; i++)			__set_bit(i, kbd->keybit);		for (i = KEY_OK; i < KEY_MAX; i++)			__set_bit(i, kbd->keybit); 		ret = input_register_device(kbd);		if (ret) {			input_free_device(kbd);			xenbus_dev_fatal(dev, ret,					 "input_register_device(kbd)");			goto error;		}		info->kbd = kbd;	} 	/* pointing device */	if (with_ptr) {		unsigned int abs; 		/* Set input abs params to match backend screen res */		abs = xenbus_read_unsigned(dev->otherend,					   XENKBD_FIELD_FEAT_ABS_POINTER, 0);		ptr_size[KPARAM_X] = xenbus_read_unsigned(dev->otherend,							  XENKBD_FIELD_WIDTH,							  ptr_size[KPARAM_X]);		ptr_size[KPARAM_Y] = xenbus_read_unsigned(dev->otherend,							  XENKBD_FIELD_HEIGHT,							  ptr_size[KPARAM_Y]);		if (abs) {			ret = xenbus_write(XBT_NIL, dev->nodename,					   XENKBD_FIELD_REQ_ABS_POINTER, "1");			if (ret) {				pr_warn("xenkbd: can't request abs-pointer\n");				abs = 0;			}		} 		ptr = input_allocate_device();		if (!ptr)			goto error_nomem;		ptr->name = "Xen Virtual Pointer";		ptr->phys = info->phys;		ptr->id.bustype = BUS_PCI;		ptr->id.vendor = 0x5853;		ptr->id.product = 0xfffe; 		if (abs) {			__set_bit(EV_ABS, ptr->evbit);			input_set_abs_params(ptr, ABS_X, 0,					     ptr_size[KPARAM_X], 0, 0);			input_set_abs_params(ptr, ABS_Y, 0,					     ptr_size[KPARAM_Y], 0, 0);		} else {			input_set_capability(ptr, EV_REL, REL_X);			input_set_capability(ptr, EV_REL, REL_Y);		}		input_set_capability(ptr, EV_REL, REL_WHEEL); 		__set_bit(EV_KEY, ptr->evbit);		for (i = BTN_LEFT; i <= BTN_TASK; i++)			__set_bit(i, ptr->keybit); 		ret = input_register_device(ptr);		if (ret) {			input_free_device(ptr);			xenbus_dev_fatal(dev, ret,					 "input_register_device(ptr)");			goto error;		}		info->ptr = ptr;	} 	/* multi-touch device */	if (with_mtouch) {		int num_cont, width, height; 		mtouch = input_allocate_device();		if (!mtouch)			goto error_nomem; 		num_cont = xenbus_read_unsigned(info->xbdev->otherend,						XENKBD_FIELD_MT_NUM_CONTACTS,						1);		width = xenbus_read_unsigned(info->xbdev->otherend,					     XENKBD_FIELD_MT_WIDTH,					     XENFB_WIDTH);		height = xenbus_read_unsigned(info->xbdev->otherend,					      XENKBD_FIELD_MT_HEIGHT,					      XENFB_HEIGHT); 		mtouch->name = "Xen Virtual Multi-touch";		mtouch->phys = info->phys;		mtouch->id.bustype = BUS_PCI;		mtouch->id.vendor = 0x5853;		mtouch->id.product = 0xfffd; 		input_set_abs_params(mtouch, ABS_MT_TOUCH_MAJOR,				     0, 255, 0, 0);		input_set_abs_params(mtouch, ABS_MT_POSITION_X,				     0, width, 0, 0);		input_set_abs_params(mtouch, ABS_MT_POSITION_Y,				     0, height, 0, 0); 		ret = input_mt_init_slots(mtouch, num_cont, INPUT_MT_DIRECT);		if (ret) {			input_free_device(mtouch);			xenbus_dev_fatal(info->xbdev, ret,					 "input_mt_init_slots");			goto error;		} 		ret = input_register_device(mtouch);		if (ret) {			input_free_device(mtouch);			xenbus_dev_fatal(info->xbdev, ret,					 "input_register_device(mtouch)");			goto error;		}		info->mtouch_cur_contact_id = -1;		info->mtouch = mtouch;	} 	if (!(with_kbd || with_ptr || with_mtouch)) {		ret = -ENXIO;		goto error;	} 	ret = xenkbd_connect_backend(dev, info);	if (ret < 0)		goto error; 	return 0;  error_nomem:	ret = -ENOMEM;	xenbus_dev_fatal(dev, ret, "allocating device memory"); error:	xenkbd_remove(dev);	return ret;} static int xenkbd_resume(struct xenbus_device *dev){	struct xenkbd_info *info = dev_get_drvdata(&dev->dev); 	xenkbd_disconnect_backend(info);	memset(info->page, 0, PAGE_SIZE);	return xenkbd_connect_backend(dev, info);} static void xenkbd_remove(struct xenbus_device *dev){	struct xenkbd_info *info = dev_get_drvdata(&dev->dev); 	xenkbd_disconnect_backend(info);	if (info->kbd)		input_unregister_device(info->kbd);	if (info->ptr)		input_unregister_device(info->ptr);	if (info->mtouch)		input_unregister_device(info->mtouch);	free_page((unsigned long)info->page);	kfree(info);} static int xenkbd_connect_backend(struct xenbus_device *dev,				  struct xenkbd_info *info){	int ret, evtchn;	struct xenbus_transaction xbt; 	ret = gnttab_grant_foreign_access(dev->otherend_id,	                                  virt_to_gfn(info->page), 0);	if (ret < 0)		return ret;	info->gref = ret; 	ret = xenbus_alloc_evtchn(dev, &evtchn);	if (ret)		goto error_grant;	ret = bind_evtchn_to_irqhandler(evtchn, input_handler,					0, dev->devicetype, info);	if (ret < 0) {		xenbus_dev_fatal(dev, ret, "bind_evtchn_to_irqhandler");		goto error_evtchan;	}	info->irq = ret;  again:	ret = xenbus_transaction_start(&xbt);	if (ret) {		xenbus_dev_fatal(dev, ret, "starting transaction");		goto error_irqh;	}	ret = xenbus_printf(xbt, dev->nodename, XENKBD_FIELD_RING_REF, "%lu",			    virt_to_gfn(info->page));	if (ret)		goto error_xenbus;	ret = xenbus_printf(xbt, dev->nodename, XENKBD_FIELD_RING_GREF,			    "%u", info->gref);	if (ret)		goto error_xenbus;	ret = xenbus_printf(xbt, dev->nodename, XENKBD_FIELD_EVT_CHANNEL, "%u",			    evtchn);	if (ret)		goto error_xenbus;	ret = xenbus_transaction_end(xbt, 0);	if (ret) {		if (ret == -EAGAIN)			goto again;		xenbus_dev_fatal(dev, ret, "completing transaction");		goto error_irqh;	} 	xenbus_switch_state(dev, XenbusStateInitialised);	return 0;  error_xenbus:	xenbus_transaction_end(xbt, 1);	xenbus_dev_fatal(dev, ret, "writing xenstore"); error_irqh:	unbind_from_irqhandler(info->irq, info);	info->irq = -1; error_evtchan:	xenbus_free_evtchn(dev, evtchn); error_grant:	gnttab_end_foreign_access(info->gref, NULL);	info->gref = -1;	return ret;} static void xenkbd_disconnect_backend(struct xenkbd_info *info){	if (info->irq >= 0)		unbind_from_irqhandler(info->irq, info);	info->irq = -1;	if (info->gref >= 0)		gnttab_end_foreign_access(info->gref, NULL);	info->gref = -1;} static void xenkbd_backend_changed(struct xenbus_device *dev,				   enum xenbus_state backend_state){

Lines 1–500 of 572.