summaryrefslogtreecommitdiffstats
path: root/drivers/virtio/virtio_input.c
blob: 350a2a5a49dbedbbfcb45e9fd3ad9be142828294 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/*
 * Generated by util/mkerr.pl DO NOT EDIT
 * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
 *
 * Licensed under the Apache License 2.0 (the "License").  You may not use
 * this file except in compliance with the License.  You can obtain a copy
 * in the file LICENSE in the source distribution or at
 * https://www.openssl.org/source/license.html
 */

#include <openssl/err.h>
#include <openssl/randerr.h>

#ifndef OPENSSL_NO_ERR

static const ERR_STRING_DATA RAND_str_reasons[] = {
    {ERR_PACK(ERR_LIB_RAND, 0, RAND_R_ADDITIONAL_INPUT_TOO_LONG),
    "additional input too long"},
    {ERR_PACK(ERR_LIB_RAND, 0, RAND_R_ALREADY_INSTANTIATED),
    "already instantiated"},
    {ERR_PACK(ERR_LIB_RAND, 0, RAND_R_ARGUMENT_OUT_OF_RANGE),
    "argument out of range"},
    {ERR_PACK(ERR_LIB_RAND, 0, RAND_R_CANNOT_OPEN_FILE), "Cannot open file"<
#include <linux/module.h>
#include <linux/virtio.h>
#include <linux/virtio_config.h>
#include <linux/input.h>

#include <uapi/linux/virtio_ids.h>
#include <uapi/linux/virtio_input.h>

struct virtio_input {
	struct virtio_device       *vdev;
	struct input_dev           *idev;
	char                       name[64];
	char                       serial[64];
	char                       phys[64];
	struct virtqueue           *evt, *sts;
	struct virtio_input_event  evts[64];
	spinlock_t                 lock;
	bool                       ready;
};

static void virtinput_queue_evtbuf(struct virtio_input *vi,
				   struct virtio_input_event *evtbuf)
{
	struct scatterlist sg[1];

	sg_init_one(sg, evtbuf, sizeof(*evtbuf));
	virtqueue_add_inbuf(vi->evt, sg, 1, evtbuf, GFP_ATOMIC);
}

static void virtinput_recv_events(struct virtqueue *vq)
{
	struct virtio_input *vi = vq->vdev->priv;
	struct virtio_input_event *event;
	unsigned long flags;
	unsigned int len;

	spin_lock_irqsave(&vi->lock, flags);
	if (vi->ready) {
		while ((event = virtqueue_get_buf(vi->evt, &len)) != NULL) {
			spin_unlock_irqrestore(&vi->lock, flags);
			input_event(vi->idev,
				    le16_to_cpu(event->type),
				    le16_to_cpu(event->code),
				    le32_to_cpu(event->value));
			spin_lock_irqsave(&vi->lock, flags);
			virtinput_queue_evtbuf(vi, event);
		}
		virtqueue_kick(vq);
	}
	spin_unlock_irqrestore(&vi->lock, flags);
}

/*
 * On error we are losing the status update, which isn't critical as
 * this is typically used for stuff like keyboard leds.
 */
static int virtinput_send_status(struct virtio_input *vi,
				 u16 type, u16 code, s32 value)
{
	struct virtio_input_event *stsbuf;
	struct scatterlist sg[1];
	unsigned long flags;
	int rc;

	stsbuf = kzalloc(sizeof(*stsbuf), GFP_ATOMIC);
	if (!stsbuf)
		return -ENOMEM;

	stsbuf->type  = cpu_to_le16(type);
	stsbuf->code  = cpu_to_le16(code);
	stsbuf->value = cpu_to_le32(value);
	sg_init_one(sg, stsbuf, sizeof(*stsbuf));

	spin_lock_irqsave(&vi->lock, flags);
	if (vi->ready) {
		rc = virtqueue_add_outbuf(vi->sts, sg, 1, stsbuf, GFP_ATOMIC);
		virtqueue_kick(vi->sts);
	} else {
		rc = -ENODEV;
	}
	spin_unlock_irqrestore(&vi->lock, flags);

	if (rc != 0)
		kfree(stsbuf);
	return rc;
}

static void virtinput_recv_status(struct virtqueue *vq)
{
	struct virtio_input *vi = vq->vdev->priv;
	struct virtio_input_event *stsbuf;
	unsigned long flags;
	unsigned int len;

	spin_lock_irqsave(&vi->lock, flags);
	while ((stsbuf = virtqueue_get_buf(vi->sts, &len)) != NULL)
		kfree(stsbuf);
	spin_unlock_irqrestore(&vi->lock, flags);
}

static int virtinput_status(struct input_dev *idev, unsigned int type,
			    unsigned int code, int value)
{
	struct virtio_input *vi = input_get_drvdata(idev);

	return virtinput_send_status(vi, type, code, value);
}

static u8 virtinput_cfg_select(struct virtio_input *vi,
			       u8 select, u8 subsel)
{
	u8 size;

	virtio_cwrite(vi->vdev, struct virtio_input_config, select, &select);
	virtio_cwrite(vi->vdev, struct virtio_input_config, subsel, &subsel);
	virtio_cread(vi->vdev, struct virtio_input_config, size, &size);
	return size;
}

static void virtinput_cfg_bits(struct virtio_input *vi, int select, int subsel,
			       unsigned long *bits, unsigned int bitcount)
{
	unsigned int bit;
	u8 *virtio_bits;
	u8 bytes;

	bytes = virtinput_cfg_select(vi, select, subsel);
	if (!bytes)
		return;
	if (bitcount > bytes * 8)
		bitcount = bytes * 8;

	/*
	 * Bitmap in virtio config space is a simple stream of bytes,
	 * with the first byte carrying bits 0-7, second bits 8-15 and
	 * so on.
	 */
	virtio_bits = kzalloc(bytes, GFP_KERNEL);
	if (!virtio_bits)
		return;
	virtio_cread_bytes(vi->vdev, offsetof(struct virtio_input_config,
					      u.bitmap),
			   virtio_bits, bytes);
	for (bit = 0; bit < bitcount; bit++) {
		if (virtio_bits[bit / 8] & (1 << (bit % 8)))
			__set_bit(bit, bits);
	}
	kfree(virtio_bits);

	if (select == VIRTIO_INPUT_CFG_EV_BITS)
		__set_bit(subsel, vi->idev->evbit);
}

static void virtinput_cfg_abs(struct virtio_input *vi, int abs)
{
	u32 mi, ma, re, fu, fl;

	virtinput_cfg_select(vi, VIRTIO_INPUT_CFG_ABS_INFO, abs);
	virtio_cread(vi->vdev, struct virtio_input_config, u.abs.min, &mi);
	virtio_cread(vi->vdev, struct virtio_input_config, u.abs.max, &ma);
	virtio_cread(vi->vdev, struct virtio_input_config, u.abs.res, &re);
	virtio_cread(vi->vdev, struct virtio_input_config, u.abs.fuzz, &fu);
	virtio_cread(vi->vdev, struct virtio_input_config, u.abs.flat, &fl);
	input_set_abs_params(vi->idev, abs, mi, ma, fu, fl);
	input_abs_set_res(vi->idev, abs, re);
}

static int virtinput_init_vqs(struct virtio_input *vi)
{
	struct virtqueue *vqs[2];
	vq_callback_t *cbs[] = { virtinput_recv_events,
				 virtinput_recv_status };
	static const char * const names[] = { "events", "status" };
	int err;

	err = vi->vdev->config->find_vqs(vi->vdev, 2, vqs, cbs, names);
	if (err)
		return err;
	vi->evt = vqs[0];
	vi->sts = vqs[1];

	return 0;
}

static void virtinput_fill_evt(struct virtio_input *vi)
{
	unsigned long flags;
	int i, size;

	spin_lock_irqsave(&vi->lock, flags);
	size = virtqueue_get_vring_size(vi->evt);
	if (size > ARRAY_SIZE(vi->evts))
		size = ARRAY_SIZE(vi->evts);
	for (i = 0; i < size; i++)
		virtinput_queue_evtbuf(vi, &vi->evts[i]);
	virtqueue_kick(vi->evt);
	spin_unlock_irqrestore(&vi->lock, flags);
}

static int virtinput_probe(struct virtio_device *vdev)
{
	struct virtio_input *vi;
	unsigned long flags;
	size_t size;
	int abs, err;

	if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1))
		return -ENODEV;

	vi = kzalloc(sizeof(*vi), GFP_KERNEL);
	if (!vi)
		return -ENOMEM;

	vdev->priv = vi;