// 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 0 xe0
#define XTKBD_EMUL1 0 xe1
#define XTKBD_RELEASE 0 x80
/*
* 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;
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 .
*/
scoped_guard(spinlock_irqsave, &kbd_dev->lock) {
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 );
}
}
/*
* 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;
guard(spinlock_irqsave)(&kbd_dev->lock);
kbd_dev->started = true ;
return 0 ;
}
static void hv_kbd_stop(struct serio *serio)
{
struct hv_kbd_dev *kbd_dev = serio->port_data;
guard(spinlock_irqsave)(&kbd_dev->lock);
kbd_dev->started = false ;
}
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);
Messung V0.5 in Prozent C=93 H=97 G=94
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-09-27)
¤
*© Formatika GbR, Deutschland