mirror of
https://github.com/torvalds/linux.git
synced 2024-11-28 23:21:31 +00:00
06b449d7f7
It is preferred to use sizeof(*pointer) instead of sizeof(type) due to the type of the variable can change and one needs not change the former (unlike the latter). This patch has no effect on runtime behavior. Signed-off-by: Erick Archer <erick.archer@outlook.com> Link: https://lore.kernel.org/r/AS8PR02MB7237D3D898CCC9C50C18DE078BFB2@AS8PR02MB7237.eurprd02.prod.outlook.com Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
441 lines
10 KiB
C
441 lines
10 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2013, Microsoft Corporation.
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/device.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/hyperv.h>
|
|
#include <linux/serio.h>
|
|
#include <linux/slab.h>
|
|
|
|
/*
|
|
* Current version 1.0
|
|
*
|
|
*/
|
|
#define SYNTH_KBD_VERSION_MAJOR 1
|
|
#define SYNTH_KBD_VERSION_MINOR 0
|
|
#define SYNTH_KBD_VERSION (SYNTH_KBD_VERSION_MINOR | \
|
|
(SYNTH_KBD_VERSION_MAJOR << 16))
|
|
|
|
|
|
/*
|
|
* Message types in the synthetic input protocol
|
|
*/
|
|
enum synth_kbd_msg_type {
|
|
SYNTH_KBD_PROTOCOL_REQUEST = 1,
|
|
SYNTH_KBD_PROTOCOL_RESPONSE = 2,
|
|
SYNTH_KBD_EVENT = 3,
|
|
SYNTH_KBD_LED_INDICATORS = 4,
|
|
};
|
|
|
|
/*
|
|
* Basic message structures.
|
|
*/
|
|
struct synth_kbd_msg_hdr {
|
|
__le32 type;
|
|
};
|
|
|
|
struct synth_kbd_msg {
|
|
struct synth_kbd_msg_hdr header;
|
|
char data[]; /* Enclosed message */
|
|
};
|
|
|
|
union synth_kbd_version {
|
|
__le32 version;
|
|
};
|
|
|
|
/*
|
|
* Protocol messages
|
|
*/
|
|
struct synth_kbd_protocol_request {
|
|
struct synth_kbd_msg_hdr header;
|
|
union synth_kbd_version version_requested;
|
|
};
|
|
|
|
#define PROTOCOL_ACCEPTED BIT(0)
|
|
struct synth_kbd_protocol_response {
|
|
struct synth_kbd_msg_hdr header;
|
|
__le32 proto_status;
|
|
};
|
|
|
|
#define IS_UNICODE BIT(0)
|
|
#define IS_BREAK BIT(1)
|
|
#define IS_E0 BIT(2)
|
|
#define IS_E1 BIT(3)
|
|
struct synth_kbd_keystroke {
|
|
struct synth_kbd_msg_hdr header;
|
|
__le16 make_code;
|
|
__le16 reserved0;
|
|
__le32 info; /* Additional information */
|
|
};
|
|
|
|
|
|
#define HK_MAXIMUM_MESSAGE_SIZE 256
|
|
|
|
#define KBD_VSC_SEND_RING_BUFFER_SIZE VMBUS_RING_SIZE(36 * 1024)
|
|
#define KBD_VSC_RECV_RING_BUFFER_SIZE VMBUS_RING_SIZE(36 * 1024)
|
|
|
|
#define XTKBD_EMUL0 0xe0
|
|
#define XTKBD_EMUL1 0xe1
|
|
#define XTKBD_RELEASE 0x80
|
|
|
|
|
|
/*
|
|
* Represents a keyboard device
|
|
*/
|
|
struct hv_kbd_dev {
|
|
struct hv_device *hv_dev;
|
|
struct serio *hv_serio;
|
|
struct synth_kbd_protocol_request protocol_req;
|
|
struct synth_kbd_protocol_response protocol_resp;
|
|
/* Synchronize the request/response if needed */
|
|
struct completion wait_event;
|
|
spinlock_t lock; /* protects 'started' field */
|
|
bool started;
|
|
};
|
|
|
|
static void hv_kbd_on_receive(struct hv_device *hv_dev,
|
|
struct synth_kbd_msg *msg, u32 msg_length)
|
|
{
|
|
struct hv_kbd_dev *kbd_dev = hv_get_drvdata(hv_dev);
|
|
struct synth_kbd_keystroke *ks_msg;
|
|
unsigned long flags;
|
|
u32 msg_type = __le32_to_cpu(msg->header.type);
|
|
u32 info;
|
|
u16 scan_code;
|
|
|
|
switch (msg_type) {
|
|
case SYNTH_KBD_PROTOCOL_RESPONSE:
|
|
/*
|
|
* Validate the information provided by the host.
|
|
* If the host is giving us a bogus packet,
|
|
* drop the packet (hoping the problem
|
|
* goes away).
|
|
*/
|
|
if (msg_length < sizeof(struct synth_kbd_protocol_response)) {
|
|
dev_err(&hv_dev->device,
|
|
"Illegal protocol response packet (len: %d)\n",
|
|
msg_length);
|
|
break;
|
|
}
|
|
|
|
memcpy(&kbd_dev->protocol_resp, msg,
|
|
sizeof(struct synth_kbd_protocol_response));
|
|
complete(&kbd_dev->wait_event);
|
|
break;
|
|
|
|
case SYNTH_KBD_EVENT:
|
|
/*
|
|
* Validate the information provided by the host.
|
|
* If the host is giving us a bogus packet,
|
|
* drop the packet (hoping the problem
|
|
* goes away).
|
|
*/
|
|
if (msg_length < sizeof(struct synth_kbd_keystroke)) {
|
|
dev_err(&hv_dev->device,
|
|
"Illegal keyboard event packet (len: %d)\n",
|
|
msg_length);
|
|
break;
|
|
}
|
|
|
|
ks_msg = (struct synth_kbd_keystroke *)msg;
|
|
info = __le32_to_cpu(ks_msg->info);
|
|
|
|
/*
|
|
* Inject the information through the serio interrupt.
|
|
*/
|
|
spin_lock_irqsave(&kbd_dev->lock, flags);
|
|
if (kbd_dev->started) {
|
|
if (info & IS_E0)
|
|
serio_interrupt(kbd_dev->hv_serio,
|
|
XTKBD_EMUL0, 0);
|
|
if (info & IS_E1)
|
|
serio_interrupt(kbd_dev->hv_serio,
|
|
XTKBD_EMUL1, 0);
|
|
scan_code = __le16_to_cpu(ks_msg->make_code);
|
|
if (info & IS_BREAK)
|
|
scan_code |= XTKBD_RELEASE;
|
|
|
|
serio_interrupt(kbd_dev->hv_serio, scan_code, 0);
|
|
}
|
|
spin_unlock_irqrestore(&kbd_dev->lock, flags);
|
|
|
|
/*
|
|
* Only trigger a wakeup on key down, otherwise
|
|
* "echo freeze > /sys/power/state" can't really enter the
|
|
* state because the Enter-UP can trigger a wakeup at once.
|
|
*/
|
|
if (!(info & IS_BREAK))
|
|
pm_wakeup_hard_event(&hv_dev->device);
|
|
|
|
break;
|
|
|
|
default:
|
|
dev_err(&hv_dev->device,
|
|
"unhandled message type %d\n", msg_type);
|
|
}
|
|
}
|
|
|
|
static void hv_kbd_handle_received_packet(struct hv_device *hv_dev,
|
|
struct vmpacket_descriptor *desc,
|
|
u32 bytes_recvd,
|
|
u64 req_id)
|
|
{
|
|
struct synth_kbd_msg *msg;
|
|
u32 msg_sz;
|
|
|
|
switch (desc->type) {
|
|
case VM_PKT_COMP:
|
|
break;
|
|
|
|
case VM_PKT_DATA_INBAND:
|
|
/*
|
|
* We have a packet that has "inband" data. The API used
|
|
* for retrieving the packet guarantees that the complete
|
|
* packet is read. So, minimally, we should be able to
|
|
* parse the payload header safely (assuming that the host
|
|
* can be trusted. Trusting the host seems to be a
|
|
* reasonable assumption because in a virtualized
|
|
* environment there is not whole lot you can do if you
|
|
* don't trust the host.
|
|
*
|
|
* Nonetheless, let us validate if the host can be trusted
|
|
* (in a trivial way). The interesting aspect of this
|
|
* validation is how do you recover if we discover that the
|
|
* host is not to be trusted? Simply dropping the packet, I
|
|
* don't think is an appropriate recovery. In the interest
|
|
* of failing fast, it may be better to crash the guest.
|
|
* For now, I will just drop the packet!
|
|
*/
|
|
|
|
msg_sz = bytes_recvd - (desc->offset8 << 3);
|
|
if (msg_sz <= sizeof(struct synth_kbd_msg_hdr)) {
|
|
/*
|
|
* Drop the packet and hope
|
|
* the problem magically goes away.
|
|
*/
|
|
dev_err(&hv_dev->device,
|
|
"Illegal packet (type: %d, tid: %llx, size: %d)\n",
|
|
desc->type, req_id, msg_sz);
|
|
break;
|
|
}
|
|
|
|
msg = (void *)desc + (desc->offset8 << 3);
|
|
hv_kbd_on_receive(hv_dev, msg, msg_sz);
|
|
break;
|
|
|
|
default:
|
|
dev_err(&hv_dev->device,
|
|
"unhandled packet type %d, tid %llx len %d\n",
|
|
desc->type, req_id, bytes_recvd);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void hv_kbd_on_channel_callback(void *context)
|
|
{
|
|
struct vmpacket_descriptor *desc;
|
|
struct hv_device *hv_dev = context;
|
|
u32 bytes_recvd;
|
|
u64 req_id;
|
|
|
|
foreach_vmbus_pkt(desc, hv_dev->channel) {
|
|
bytes_recvd = desc->len8 * 8;
|
|
req_id = desc->trans_id;
|
|
|
|
hv_kbd_handle_received_packet(hv_dev, desc, bytes_recvd,
|
|
req_id);
|
|
}
|
|
}
|
|
|
|
static int hv_kbd_connect_to_vsp(struct hv_device *hv_dev)
|
|
{
|
|
struct hv_kbd_dev *kbd_dev = hv_get_drvdata(hv_dev);
|
|
struct synth_kbd_protocol_request *request;
|
|
struct synth_kbd_protocol_response *response;
|
|
u32 proto_status;
|
|
int error;
|
|
|
|
reinit_completion(&kbd_dev->wait_event);
|
|
|
|
request = &kbd_dev->protocol_req;
|
|
memset(request, 0, sizeof(struct synth_kbd_protocol_request));
|
|
request->header.type = __cpu_to_le32(SYNTH_KBD_PROTOCOL_REQUEST);
|
|
request->version_requested.version = __cpu_to_le32(SYNTH_KBD_VERSION);
|
|
|
|
error = vmbus_sendpacket(hv_dev->channel, request,
|
|
sizeof(struct synth_kbd_protocol_request),
|
|
(unsigned long)request,
|
|
VM_PKT_DATA_INBAND,
|
|
VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED);
|
|
if (error)
|
|
return error;
|
|
|
|
if (!wait_for_completion_timeout(&kbd_dev->wait_event, 10 * HZ))
|
|
return -ETIMEDOUT;
|
|
|
|
response = &kbd_dev->protocol_resp;
|
|
proto_status = __le32_to_cpu(response->proto_status);
|
|
if (!(proto_status & PROTOCOL_ACCEPTED)) {
|
|
dev_err(&hv_dev->device,
|
|
"synth_kbd protocol request failed (version %d)\n",
|
|
SYNTH_KBD_VERSION);
|
|
return -ENODEV;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hv_kbd_start(struct serio *serio)
|
|
{
|
|
struct hv_kbd_dev *kbd_dev = serio->port_data;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&kbd_dev->lock, flags);
|
|
kbd_dev->started = true;
|
|
spin_unlock_irqrestore(&kbd_dev->lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void hv_kbd_stop(struct serio *serio)
|
|
{
|
|
struct hv_kbd_dev *kbd_dev = serio->port_data;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&kbd_dev->lock, flags);
|
|
kbd_dev->started = false;
|
|
spin_unlock_irqrestore(&kbd_dev->lock, flags);
|
|
}
|
|
|
|
static int hv_kbd_probe(struct hv_device *hv_dev,
|
|
const struct hv_vmbus_device_id *dev_id)
|
|
{
|
|
struct hv_kbd_dev *kbd_dev;
|
|
struct serio *hv_serio;
|
|
int error;
|
|
|
|
kbd_dev = kzalloc(sizeof(*kbd_dev), GFP_KERNEL);
|
|
hv_serio = kzalloc(sizeof(*hv_serio), GFP_KERNEL);
|
|
if (!kbd_dev || !hv_serio) {
|
|
error = -ENOMEM;
|
|
goto err_free_mem;
|
|
}
|
|
|
|
kbd_dev->hv_dev = hv_dev;
|
|
kbd_dev->hv_serio = hv_serio;
|
|
spin_lock_init(&kbd_dev->lock);
|
|
init_completion(&kbd_dev->wait_event);
|
|
hv_set_drvdata(hv_dev, kbd_dev);
|
|
|
|
hv_serio->dev.parent = &hv_dev->device;
|
|
hv_serio->id.type = SERIO_8042_XL;
|
|
hv_serio->port_data = kbd_dev;
|
|
strscpy(hv_serio->name, dev_name(&hv_dev->device),
|
|
sizeof(hv_serio->name));
|
|
strscpy(hv_serio->phys, dev_name(&hv_dev->device),
|
|
sizeof(hv_serio->phys));
|
|
|
|
hv_serio->start = hv_kbd_start;
|
|
hv_serio->stop = hv_kbd_stop;
|
|
|
|
error = vmbus_open(hv_dev->channel,
|
|
KBD_VSC_SEND_RING_BUFFER_SIZE,
|
|
KBD_VSC_RECV_RING_BUFFER_SIZE,
|
|
NULL, 0,
|
|
hv_kbd_on_channel_callback,
|
|
hv_dev);
|
|
if (error)
|
|
goto err_free_mem;
|
|
|
|
error = hv_kbd_connect_to_vsp(hv_dev);
|
|
if (error)
|
|
goto err_close_vmbus;
|
|
|
|
serio_register_port(kbd_dev->hv_serio);
|
|
|
|
device_init_wakeup(&hv_dev->device, true);
|
|
|
|
return 0;
|
|
|
|
err_close_vmbus:
|
|
vmbus_close(hv_dev->channel);
|
|
err_free_mem:
|
|
kfree(hv_serio);
|
|
kfree(kbd_dev);
|
|
return error;
|
|
}
|
|
|
|
static void hv_kbd_remove(struct hv_device *hv_dev)
|
|
{
|
|
struct hv_kbd_dev *kbd_dev = hv_get_drvdata(hv_dev);
|
|
|
|
serio_unregister_port(kbd_dev->hv_serio);
|
|
vmbus_close(hv_dev->channel);
|
|
kfree(kbd_dev);
|
|
|
|
hv_set_drvdata(hv_dev, NULL);
|
|
}
|
|
|
|
static int hv_kbd_suspend(struct hv_device *hv_dev)
|
|
{
|
|
vmbus_close(hv_dev->channel);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hv_kbd_resume(struct hv_device *hv_dev)
|
|
{
|
|
int ret;
|
|
|
|
ret = vmbus_open(hv_dev->channel,
|
|
KBD_VSC_SEND_RING_BUFFER_SIZE,
|
|
KBD_VSC_RECV_RING_BUFFER_SIZE,
|
|
NULL, 0,
|
|
hv_kbd_on_channel_callback,
|
|
hv_dev);
|
|
if (ret == 0)
|
|
ret = hv_kbd_connect_to_vsp(hv_dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct hv_vmbus_device_id id_table[] = {
|
|
/* Keyboard guid */
|
|
{ HV_KBD_GUID, },
|
|
{ },
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(vmbus, id_table);
|
|
|
|
static struct hv_driver hv_kbd_drv = {
|
|
.name = KBUILD_MODNAME,
|
|
.id_table = id_table,
|
|
.probe = hv_kbd_probe,
|
|
.remove = hv_kbd_remove,
|
|
.suspend = hv_kbd_suspend,
|
|
.resume = hv_kbd_resume,
|
|
.driver = {
|
|
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
},
|
|
};
|
|
|
|
static int __init hv_kbd_init(void)
|
|
{
|
|
return vmbus_driver_register(&hv_kbd_drv);
|
|
}
|
|
|
|
static void __exit hv_kbd_exit(void)
|
|
{
|
|
vmbus_driver_unregister(&hv_kbd_drv);
|
|
}
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("Microsoft Hyper-V Synthetic Keyboard Driver");
|
|
|
|
module_init(hv_kbd_init);
|
|
module_exit(hv_kbd_exit);
|