From e2eb39e6f61fb1c99c01a4e3b426caf98c828d51 Mon Sep 17 00:00:00 2001 From: Sorgelig Date: Fri, 17 Apr 2026 18:20:01 +0800 Subject: [PATCH] xone: update driver. --- drivers/hid/xone/Makefile | 12 +- drivers/hid/xone/auth/auth.c | 665 ++++++++++++++++++++++++ drivers/hid/xone/auth/auth.h | 49 ++ drivers/hid/xone/auth/crypto.c | 290 +++++++++++ drivers/hid/xone/auth/crypto.h | 21 + drivers/hid/xone/bus/bus.c | 224 ++++---- drivers/hid/xone/bus/bus.h | 42 +- drivers/hid/xone/bus/protocol.c | 618 ++++++++++++++-------- drivers/hid/xone/bus/protocol.h | 35 +- drivers/hid/xone/driver/chatpad.c | 9 +- drivers/hid/xone/driver/common.c | 8 +- drivers/hid/xone/driver/common.h | 2 +- drivers/hid/xone/driver/gamepad.c | 115 ++-- drivers/hid/xone/driver/headset.c | 279 +++++----- drivers/hid/xone/driver/madcatz_glam.c | 206 ++++++++ drivers/hid/xone/driver/madcatz_strat.c | 203 ++++++++ drivers/hid/xone/driver/pdp_jaguar.c | 206 ++++++++ drivers/hid/xone/transport/dongle.c | 165 +++--- drivers/hid/xone/transport/mt76.c | 126 ++++- drivers/hid/xone/transport/mt76.h | 18 +- drivers/hid/xone/transport/mt76_defs.h | 14 +- drivers/hid/xone/transport/wired.c | 45 +- 22 files changed, 2676 insertions(+), 676 deletions(-) create mode 100644 drivers/hid/xone/auth/auth.c create mode 100644 drivers/hid/xone/auth/auth.h create mode 100644 drivers/hid/xone/auth/crypto.c create mode 100644 drivers/hid/xone/auth/crypto.h create mode 100644 drivers/hid/xone/driver/madcatz_glam.c create mode 100644 drivers/hid/xone/driver/madcatz_strat.c create mode 100644 drivers/hid/xone/driver/pdp_jaguar.c diff --git a/drivers/hid/xone/Makefile b/drivers/hid/xone/Makefile index 1d2c428a0..4cc2638a4 100644 --- a/drivers/hid/xone/Makefile +++ b/drivers/hid/xone/Makefile @@ -1,8 +1,12 @@ xone-wired-y := transport/wired.o xone-dongle-y := transport/dongle.o transport/mt76.o -xone-gip-bus-y := bus/bus.o bus/protocol.o -xone-gip-common-y := driver/common.o -xone-gip-gamepad-y := driver/gamepad.o +xone-gip-bus-y := bus/bus.o bus/protocol.o auth/auth.o auth/crypto.o +xone-gip-gamepad-y := driver/gamepad.o driver/common.o xone-gip-headset-y := driver/headset.o xone-gip-chatpad-y := driver/chatpad.o -obj-$(CONFIG_JOYSTICK_XONE) := xone-wired.o xone-dongle.o xone-gip-bus.o xone-gip-common.o xone-gip-gamepad.o xone-gip-headset.o xone-gip-chatpad.o +xone-gip-madcatz_glam-y := driver/madcatz_glam.o +xone-gip-madcatz_strat-y := driver/madcatz_strat.o +xone-gip-pdp_jaguar-y := driver/pdp_jaguar.o + +obj-$(CONFIG_JOYSTICK_XONE) := xone-wired.o xone-dongle.o xone-gip-bus.o xone-gip-gamepad.o xone-gip-headset.o xone-gip-chatpad.o xone-gip-madcatz_glam.o xone-gip-madcatz_strat.o xone-gip-pdp_jaguar.o + diff --git a/drivers/hid/xone/auth/auth.c b/drivers/hid/xone/auth/auth.c new file mode 100644 index 000000000..bee93031d --- /dev/null +++ b/drivers/hid/xone/auth/auth.c @@ -0,0 +1,665 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 Severin von Wnuck-Lipinski + */ + +#include +#include + +#include "auth.h" +#include "crypto.h" +#include "../bus/bus.h" + +enum gip_auth_context { + GIP_AUTH_CTX_HANDSHAKE = 0x00, + GIP_AUTH_CTX_CONTROL = 0x01, +}; + +enum gip_auth_command_handshake { + GIP_AUTH_CMD_HOST_HELLO = 0x01, + GIP_AUTH_CMD_CLIENT_HELLO = 0x02, + GIP_AUTH_CMD_CLIENT_CERTIFICATE = 0x03, + GIP_AUTH_CMD_HOST_SECRET = 0x05, + GIP_AUTH_CMD_HOST_FINISH = 0x07, + GIP_AUTH_CMD_CLIENT_FINISH = 0x08, + + GIP_AUTH2_CMD_HOST_HELLO = 0x21, + GIP_AUTH2_CMD_CLIENT_HELLO = 0x22, + GIP_AUTH2_CMD_CLIENT_CERTIFICATE = 0x23, + GIP_AUTH2_CMD_CLIENT_PUBKEY = 0x24, + GIP_AUTH2_CMD_HOST_PUBKEY = 0x25, + GIP_AUTH2_CMD_HOST_FINISH = 0x26, + GIP_AUTH2_CMD_CLIENT_FINISH = 0x27, +}; + +enum gip_auth_command_control { + GIP_AUTH_CTRL_COMPLETE = 0x00, + GIP_AUTH_CTRL_RESET = 0x01, +}; + +enum gip_auth_option { + GIP_AUTH_OPT_ACKNOWLEDGE = BIT(0), + GIP_AUTH_OPT_REQUEST = BIT(1), + GIP_AUTH_OPT_FROM_HOST = BIT(6), + GIP_AUTH_OPT_FROM_CLIENT = BIT(6) | BIT(7), +}; + +struct gip_auth_header_handshake { + u8 context; + u8 options; + u8 error; + u8 command; + __be16 length; +} __packed; + +struct gip_auth_header_data { + u8 command; + u8 version; + __be16 length; +} __packed; + +struct gip_auth_header_full { + struct gip_auth_header_handshake handshake; + struct gip_auth_header_data data; +} __packed; + +struct gip_auth_header_control { + u8 context; + u8 control; +} __packed; + +struct gip_auth_request { + struct gip_auth_header_handshake header; + + u8 trailer[GIP_AUTH_TRAILER_LEN]; +} __packed; + +struct gip_auth_pkt_host_hello { + struct gip_auth_header_full header; + + u8 random[GIP_AUTH_RANDOM_LEN]; + u8 unknown1[4]; + u8 unknown2[4]; + + u8 trailer[GIP_AUTH_TRAILER_LEN]; +} __packed; + +struct gip_auth_pkt_host_secret { + struct gip_auth_header_full header; + + u8 encrypted_pms[GIP_AUTH_ENCRYPTED_PMS_LEN]; + + u8 trailer[GIP_AUTH_TRAILER_LEN]; +} __packed; + +struct gip_auth_pkt_host_finish { + struct gip_auth_header_full header; + + u8 transcript[GIP_AUTH_TRANSCRIPT_LEN]; + + u8 trailer[GIP_AUTH_TRAILER_LEN]; +} __packed; + +struct gip_auth_pkt_client_hello { + u8 random[GIP_AUTH_RANDOM_LEN]; + u8 unknown[48]; +} __packed; + +struct gip_auth_pkt_client_finish { + u8 transcript[GIP_AUTH_TRANSCRIPT_LEN]; + u8 unknown[32]; +} __packed; + +struct gip_auth2_pkt_host_hello { + struct gip_auth_header_full header; + + u8 random[GIP_AUTH_RANDOM_LEN]; + u8 unknown[4]; + + u8 trailer[GIP_AUTH_TRAILER_LEN]; +} __packed; + +struct gip_auth2_pkt_host_pubkey { + struct gip_auth_header_full header; + + u8 pubkey[GIP_AUTH2_PUBKEY_LEN]; + + u8 trailer[GIP_AUTH_TRAILER_LEN]; +} __packed; + +struct gip_auth2_pkt_host_finish { + struct gip_auth_header_full header; + + u8 transcript[GIP_AUTH_TRANSCRIPT_LEN]; + + u8 trailer[GIP_AUTH_TRAILER_LEN]; +} __packed; + +struct gip_auth2_pkt_client_hello { + u8 random[GIP_AUTH_RANDOM_LEN]; + u8 unknown1[108]; + u8 unknown2[32]; +} __packed; + +struct gip_auth2_pkt_client_cert { + char header[4]; + u8 unknown1[136]; + char chip[32]; + char revision[20]; + u8 unknown2[576]; +} __packed; + +struct gip_auth2_pkt_client_pubkey { + u8 pubkey[GIP_AUTH2_PUBKEY_LEN]; + u8 unknown[64]; +} __packed; + +struct gip_auth2_pkt_client_finish { + u8 transcript[GIP_AUTH_TRANSCRIPT_LEN]; + u8 unknown[32]; +} __packed; + +static int gip_auth_send_pkt(struct gip_auth *auth, + enum gip_auth_command_handshake cmd, + void *pkt, u16 len) +{ + struct gip_auth_header_full *hdr = pkt; + u16 data_len = len - sizeof(hdr->handshake) - GIP_AUTH_TRAILER_LEN; + + hdr->handshake.context = GIP_AUTH_CTX_HANDSHAKE; + hdr->handshake.options = GIP_AUTH_OPT_ACKNOWLEDGE | + GIP_AUTH_OPT_FROM_HOST; + hdr->handshake.command = cmd; + hdr->handshake.length = cpu_to_be16(data_len); + + hdr->data.command = cmd; + hdr->data.version = cmd >= GIP_AUTH2_CMD_HOST_HELLO ? 0x02 : 0x01; + hdr->data.length = cpu_to_be16(data_len - sizeof(hdr->data)); + + auth->last_sent_command = cmd; + crypto_shash_update(auth->shash_transcript, + pkt + sizeof(hdr->handshake), data_len); + + return gip_send_authenticate(auth->client, pkt, len, true); +} + +static int gip_auth_request_pkt(struct gip_auth *auth, + enum gip_auth_command_handshake cmd, u16 len) +{ + struct gip_auth_request req = {}; + u16 data_len = len + sizeof(struct gip_auth_header_data); + + req.header.context = GIP_AUTH_CTX_HANDSHAKE; + req.header.options = GIP_AUTH_OPT_REQUEST | GIP_AUTH_OPT_FROM_HOST; + req.header.command = cmd; + req.header.length = cpu_to_be16(data_len); + + return gip_send_authenticate(auth->client, &req, sizeof(req), true); +} + +static int gip_auth2_send_hello(struct gip_auth *auth) +{ + struct gip_auth2_pkt_host_hello pkt = {}; + + get_random_bytes(auth->random_host, sizeof(auth->random_host)); + memcpy(pkt.random, auth->random_host, sizeof(pkt.random)); + + return gip_auth_send_pkt(auth, GIP_AUTH2_CMD_HOST_HELLO, + &pkt, sizeof(pkt)); +} + +static int gip_auth2_handle_pkt_hello(struct gip_auth *auth, + void *data, u32 len) +{ + struct gip_auth2_pkt_client_hello *pkt = data; + + if (len < sizeof(*pkt)) + return -EINVAL; + + memcpy(auth->random_client, pkt->random, sizeof(auth->random_client)); + + return gip_auth_request_pkt(auth, GIP_AUTH2_CMD_CLIENT_CERTIFICATE, + sizeof(struct gip_auth2_pkt_client_cert)); +} + +static int gip_auth2_handle_pkt_certificate(struct gip_auth *auth, + void *data, u32 len) +{ + struct gip_auth2_pkt_client_cert *pkt = data; + + if (len < sizeof(*pkt)) + return -EINVAL; + + dev_dbg(&auth->client->dev, + "%s: header=%.*s, chip=%.*s, revision=%.*s\n", __func__, + (int)sizeof(pkt->header), pkt->header, + (int)sizeof(pkt->chip), pkt->chip, + (int)sizeof(pkt->revision), pkt->revision); + + return gip_auth_request_pkt(auth, GIP_AUTH2_CMD_CLIENT_PUBKEY, + sizeof(struct gip_auth2_pkt_client_pubkey)); +} + +static int gip_auth2_handle_pkt_pubkey(struct gip_auth *auth, + void *data, u32 len) +{ + struct gip_auth2_pkt_client_pubkey *pkt = data; + + if (len < sizeof(*pkt)) + return -EINVAL; + + memcpy(auth->pubkey_client2, pkt->pubkey, sizeof(pkt->pubkey)); + schedule_work(&auth->work_exchange_ecdh); + + return 0; +} + +static void gip_auth2_exchange_ecdh(struct work_struct *work) +{ + struct gip_auth *auth = container_of(work, typeof(*auth), + work_exchange_ecdh); + struct gip_auth2_pkt_host_pubkey pkt = {}; + u8 random[GIP_AUTH_RANDOM_LEN * 2]; + u8 secret[GIP_AUTH2_SECRET_LEN]; + int err; + + memcpy(random, auth->random_host, sizeof(auth->random_host)); + memcpy(random + sizeof(auth->random_host), auth->random_client, + sizeof(auth->random_client)); + + err = gip_auth_compute_ecdh(auth->pubkey_client2, pkt.pubkey, + sizeof(pkt.pubkey), secret); + if (err) { + dev_err(&auth->client->dev, "%s: compute ECDH failed: %d\n", + __func__, err); + return; + } + + err = gip_auth_compute_prf(auth->shash_prf, "Master Secret", + secret, sizeof(secret), + random, sizeof(random), + auth->master_secret, + sizeof(auth->master_secret)); + if (err) { + dev_err(&auth->client->dev, "%s: compute PRF failed: %d\n", + __func__, err); + return; + } + + err = gip_auth_send_pkt(auth, GIP_AUTH2_CMD_HOST_PUBKEY, + &pkt, sizeof(pkt)); + if (err) + dev_err(&auth->client->dev, "%s: send pkt failed: %d\n", + __func__, err); +} + +static int gip_auth_send_pkt_hello(struct gip_auth *auth) +{ + struct gip_auth_pkt_host_hello pkt = {}; + + get_random_bytes(auth->random_host, sizeof(auth->random_host)); + memcpy(pkt.random, auth->random_host, sizeof(pkt.random)); + + return gip_auth_send_pkt(auth, GIP_AUTH_CMD_HOST_HELLO, + &pkt, sizeof(pkt)); +} + +static int gip_auth_send_pkt_finish(struct gip_auth *auth, + enum gip_auth_command_handshake cmd) +{ + struct gip_auth_pkt_host_finish pkt = {}; + u8 transcript[GIP_AUTH_TRANSCRIPT_LEN]; + int err; + + err = gip_auth_get_transcript(auth->shash_transcript, transcript); + if (err) { + dev_err(&auth->client->dev, "%s: get transcript failed: %d\n", + __func__, err); + return err; + } + + err = gip_auth_compute_prf(auth->shash_prf, "Host Finished", + auth->master_secret, + sizeof(auth->master_secret), + transcript, sizeof(transcript), + pkt.transcript, sizeof(pkt.transcript)); + if (err) { + dev_err(&auth->client->dev, "%s: compute PRF failed: %d\n", + __func__, err); + return err; + } + + return gip_auth_send_pkt(auth, cmd, &pkt, sizeof(pkt)); +} + +static int gip_auth_handle_pkt_acknowledge(struct gip_auth *auth) +{ + switch (auth->last_sent_command) { + case GIP_AUTH2_CMD_HOST_HELLO: + return gip_auth_request_pkt(auth, GIP_AUTH2_CMD_CLIENT_HELLO, + sizeof(struct gip_auth2_pkt_client_hello)); + case GIP_AUTH2_CMD_HOST_PUBKEY: + return gip_auth_send_pkt_finish(auth, GIP_AUTH2_CMD_HOST_FINISH); + case GIP_AUTH2_CMD_HOST_FINISH: + return gip_auth_request_pkt(auth, GIP_AUTH2_CMD_CLIENT_FINISH, + sizeof(struct gip_auth2_pkt_client_finish)); + case GIP_AUTH_CMD_HOST_HELLO: + return gip_auth_request_pkt(auth, GIP_AUTH_CMD_CLIENT_HELLO, + sizeof(struct gip_auth_pkt_client_hello)); + case GIP_AUTH_CMD_HOST_SECRET: + return gip_auth_send_pkt_finish(auth, GIP_AUTH_CMD_HOST_FINISH); + case GIP_AUTH_CMD_HOST_FINISH: + return gip_auth_request_pkt(auth, GIP_AUTH_CMD_CLIENT_FINISH, + sizeof(struct gip_auth_pkt_client_finish)); + default: + return -EPROTO; + } +} + +static int gip_auth_handle_pkt_hello(struct gip_auth *auth, + void *data, u32 len) +{ + struct gip_auth_pkt_client_hello *pkt = data; + + if (len < sizeof(*pkt)) + return -EINVAL; + + memcpy(auth->random_client, pkt->random, sizeof(pkt->random)); + + return gip_auth_request_pkt(auth, GIP_AUTH_CMD_CLIENT_CERTIFICATE, + GIP_AUTH_CERTIFICATE_MAX_LEN); +} + +static int gip_auth_handle_pkt_certificate(struct gip_auth *auth, + void *data, u32 len) +{ + /* ASN.1 SEQUENCE (len = 0x04 + 0x010a) */ + u8 asn1_seq[] = { 0x30, 0x82, 0x01, 0x0a }; + int i; + + if (len > GIP_AUTH_CERTIFICATE_MAX_LEN) + return -EINVAL; + + /* + * Poor way of extracting a pubkey from an X.509 certificate. + * The certificates issued by Microsoft do not comply with RFC 5280. + * They have an empty subject and no subjectAltName. + * This is explicitly forbidden by section 4.2.1.6 of the RFC. + * The kernel's ASN.1 parser will fail when using x509_cert_parse. + */ + for (i = 0; i + sizeof(asn1_seq) <= len; i++) { + if (memcmp(data + i, asn1_seq, sizeof(asn1_seq))) + continue; + + if (i + GIP_AUTH_PUBKEY_LEN > len) + return -EINVAL; + + memcpy(auth->pubkey_client, data + i, GIP_AUTH_PUBKEY_LEN); + schedule_work(&auth->work_exchange_rsa); + + return 0; + } + + return -EPROTO; +} + +static int gip_auth_handle_pkt_finish(struct gip_auth *auth, + void *data, u32 len) +{ + struct gip_auth_pkt_client_finish *pkt = data; + u8 transcript[GIP_AUTH_TRANSCRIPT_LEN]; + u8 finished[GIP_AUTH_TRANSCRIPT_LEN]; + int err; + + if (len < sizeof(*pkt)) + return -EINVAL; + + err = gip_auth_get_transcript(auth->shash_transcript, transcript); + if (err) { + dev_err(&auth->client->dev, "%s: get transcript failed: %d\n", + __func__, err); + return err; + } + + err = gip_auth_compute_prf(auth->shash_prf, "Device Finished", + auth->master_secret, + sizeof(auth->master_secret), + transcript, sizeof(transcript), + finished, sizeof(finished)); + if (err) { + dev_err(&auth->client->dev, "%s: compute PRF failed: %d\n", + __func__, err); + return err; + } + + if (memcmp(pkt->transcript, finished, sizeof(finished))) { + dev_err(&auth->client->dev, "%s: transcript mismatch\n", + __func__); + return -EPROTO; + } + + schedule_work(&auth->work_complete); + + return 0; +} + +static void gip_auth_exchange_rsa(struct work_struct *work) +{ + struct gip_auth *auth = container_of(work, typeof(*auth), + work_exchange_rsa); + struct gip_auth_pkt_host_secret pkt = {}; + u8 random[GIP_AUTH_RANDOM_LEN * 2]; + u8 pms[GIP_AUTH_SECRET_LEN]; + int err; + + memcpy(random, auth->random_host, sizeof(auth->random_host)); + memcpy(random + sizeof(auth->random_host), auth->random_client, + sizeof(auth->random_client)); + + /* get random premaster secret */ + get_random_bytes(pms, sizeof(pms)); + + err = gip_auth_encrypt_rsa(auth->pubkey_client, + sizeof(auth->pubkey_client), + pms, sizeof(pms), pkt.encrypted_pms, + sizeof(pkt.encrypted_pms)); + if (err) { + dev_err(&auth->client->dev, "%s: encrypt RSA failed: %d\n", + __func__, err); + return; + } + + err = gip_auth_compute_prf(auth->shash_prf, "Master Secret", + pms, sizeof(pms), random, sizeof(random), + auth->master_secret, + sizeof(auth->master_secret)); + if (err) { + dev_err(&auth->client->dev, "%s: compute PRF failed: %d\n", + __func__, err); + return; + } + + err = gip_auth_send_pkt(auth, GIP_AUTH_CMD_HOST_SECRET, + &pkt, sizeof(pkt)); + if (err) + dev_err(&auth->client->dev, "%s: send pkt failed: %d\n", + __func__, err); +} + +int gip_auth_send_complete(struct gip_client *client) +{ + struct gip_auth_header_control hdr = {}; + + hdr.context = GIP_AUTH_CTX_CONTROL; + hdr.control = GIP_AUTH_CTRL_COMPLETE; + + return gip_send_authenticate(client, &hdr, sizeof(hdr), false); +} +EXPORT_SYMBOL_GPL(gip_auth_send_complete); + +static void gip_auth_complete_handshake(struct work_struct *work) +{ + struct gip_auth *auth = container_of(work, typeof(*auth), + work_complete); + u8 random[GIP_AUTH_RANDOM_LEN * 2]; + u8 key[GIP_AUTH_SESSION_KEY_LEN]; + int err; + + memcpy(random, auth->random_host, sizeof(auth->random_host)); + memcpy(random + sizeof(auth->random_host), auth->random_client, + sizeof(auth->random_client)); + + err = gip_auth_compute_prf(auth->shash_prf, + "EXPORTER DAWN data channel session key for controller", + auth->master_secret, + sizeof(auth->master_secret), + random, sizeof(random), + key, sizeof(key)); + if (err) { + dev_err(&auth->client->dev, "%s: compute PRF failed: %d\n", + __func__, err); + return; + } + + dev_dbg(&auth->client->dev, "%s: key=%*phD\n", __func__, + (int)sizeof(key), key); + + err = gip_auth_send_complete(auth->client); + if (err) { + dev_err(&auth->client->dev, "%s: send complete failed: %d\n", + __func__, err); + return; + } + + err = gip_set_encryption_key(auth->client, key, sizeof(key)); + if (err) + dev_err(&auth->client->dev, + "%s: set encryption key failed: %d\n", __func__, err); +} + +static int gip_auth_dispatch_pkt(struct gip_auth *auth, + enum gip_auth_command_handshake cmd, + void *data, u32 len) +{ + switch (cmd) { + case GIP_AUTH2_CMD_CLIENT_HELLO: + return gip_auth2_handle_pkt_hello(auth, data, len); + case GIP_AUTH2_CMD_CLIENT_CERTIFICATE: + return gip_auth2_handle_pkt_certificate(auth, data, len); + case GIP_AUTH2_CMD_CLIENT_PUBKEY: + return gip_auth2_handle_pkt_pubkey(auth, data, len); + case GIP_AUTH2_CMD_CLIENT_FINISH: + return gip_auth_handle_pkt_finish(auth, data, len); + case GIP_AUTH_CMD_CLIENT_HELLO: + return gip_auth_handle_pkt_hello(auth, data, len); + case GIP_AUTH_CMD_CLIENT_CERTIFICATE: + return gip_auth_handle_pkt_certificate(auth, data, len); + case GIP_AUTH_CMD_CLIENT_FINISH: + return gip_auth_handle_pkt_finish(auth, data, len); + default: + return -EPROTO; + } +} + +static int gip_auth_process_pkt_data(struct gip_auth *auth, void *data, u32 len) +{ + struct gip_auth_header_full *hdr = data; + int err; + + if (len < sizeof(*hdr)) + return -EINVAL; + + /* client uses auth v2 */ + if (hdr->handshake.command != hdr->data.command) { + /* reset transcript hash and restart handshake */ + dev_dbg(&auth->client->dev, "%s: protocol upgrade\n", __func__); + crypto_shash_init(auth->shash_transcript); + return gip_auth2_send_hello(auth); + } + + err = gip_auth_dispatch_pkt(auth, hdr->data.command, + data + sizeof(*hdr), len - sizeof(*hdr)); + if (err) + return err; + + return crypto_shash_update(auth->shash_transcript, + data + sizeof(hdr->handshake), + len - sizeof(hdr->handshake)); +} + +int gip_auth_process_pkt(struct gip_auth *auth, void *data, u32 len) +{ + struct gip_auth_header_handshake *hdr = data; + + if (!auth->client) + return -ENODEV; + + if (len < sizeof(*hdr)) + return -EINVAL; + + if (hdr->error) + return -EPROTO; + + if (hdr->options & GIP_AUTH_OPT_ACKNOWLEDGE) { + if (hdr->command == 0x01) + return gip_auth_handle_pkt_acknowledge(auth); + + dev_err(&auth->client->dev, "%s: handshake failed: 0x%02x\n", + __func__, hdr->command); + return -EPROTO; + } + + return gip_auth_process_pkt_data(auth, data, len); +} +EXPORT_SYMBOL_GPL(gip_auth_process_pkt); + +static void gip_auth_release(void *res) +{ + struct gip_auth *auth = res; + + cancel_work_sync(&auth->work_exchange_rsa); + cancel_work_sync(&auth->work_exchange_ecdh); + cancel_work_sync(&auth->work_complete); + + crypto_free_shash(auth->shash_transcript->tfm); + crypto_free_shash(auth->shash_prf->tfm); + kfree(auth->shash_transcript); + kfree(auth->shash_prf); + + auth->client = NULL; + auth->shash_transcript = NULL; + auth->shash_prf = NULL; +} + +int gip_auth_start_handshake(struct gip_auth *auth, struct gip_client *client) +{ + struct shash_desc *shash_transcript, *shash_prf; + int err; + + shash_transcript = gip_auth_alloc_shash("sha256"); + if (IS_ERR(shash_transcript)) + return PTR_ERR(shash_transcript); + + shash_prf = gip_auth_alloc_shash("hmac(sha256)"); + if (IS_ERR(shash_prf)) { + crypto_free_shash(shash_transcript->tfm); + kfree(shash_transcript); + return PTR_ERR(shash_prf); + } + + auth->client = client; + auth->shash_transcript = shash_transcript; + auth->shash_prf = shash_prf; + + INIT_WORK(&auth->work_exchange_rsa, gip_auth_exchange_rsa); + INIT_WORK(&auth->work_exchange_ecdh, gip_auth2_exchange_ecdh); + INIT_WORK(&auth->work_complete, gip_auth_complete_handshake); + + err = devm_add_action_or_reset(&client->dev, gip_auth_release, auth); + if (err) + return err; + + return gip_auth_send_pkt_hello(auth); +} +EXPORT_SYMBOL_GPL(gip_auth_start_handshake); + diff --git a/drivers/hid/xone/auth/auth.h b/drivers/hid/xone/auth/auth.h new file mode 100644 index 000000000..e9197bfd0 --- /dev/null +++ b/drivers/hid/xone/auth/auth.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2023 Severin von Wnuck-Lipinski + */ + +#pragma once + +#include +#include + +/* trailer is required for v1 clients */ +#define GIP_AUTH_TRAILER_LEN 8 +#define GIP_AUTH_RANDOM_LEN 32 +#define GIP_AUTH_CERTIFICATE_MAX_LEN 1024 +#define GIP_AUTH_PUBKEY_LEN 270 +#define GIP_AUTH_SECRET_LEN 48 +#define GIP_AUTH_ENCRYPTED_PMS_LEN 256 +#define GIP_AUTH_TRANSCRIPT_LEN 32 +#define GIP_AUTH_SESSION_KEY_LEN 16 + +#define GIP_AUTH2_PUBKEY_LEN 64 +#define GIP_AUTH2_SECRET_LEN 32 + +struct gip_client; + +struct gip_auth { + struct gip_client *client; + + struct shash_desc *shash_transcript; + struct shash_desc *shash_prf; + + struct work_struct work_exchange_rsa; + struct work_struct work_exchange_ecdh; + struct work_struct work_complete; + + u8 last_sent_command; + + u8 random_host[GIP_AUTH_RANDOM_LEN]; + u8 random_client[GIP_AUTH_RANDOM_LEN]; + + u8 pubkey_client[GIP_AUTH_PUBKEY_LEN]; + u8 pubkey_client2[GIP_AUTH2_PUBKEY_LEN]; + + u8 master_secret[GIP_AUTH_SECRET_LEN]; +}; + +int gip_auth_send_complete(struct gip_client *client); +int gip_auth_process_pkt(struct gip_auth *auth, void *data, u32 len); +int gip_auth_start_handshake(struct gip_auth *auth, struct gip_client *client); diff --git a/drivers/hid/xone/auth/crypto.c b/drivers/hid/xone/auth/crypto.c new file mode 100644 index 000000000..cafef0603 --- /dev/null +++ b/drivers/hid/xone/auth/crypto.c @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 Severin von Wnuck-Lipinski + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "crypto.h" + +#define GIP_AUTH_ECDH_SECRET_LEN 32 + +struct shash_desc *gip_auth_alloc_shash(const char *alg) +{ + struct crypto_shash *tfm; + struct shash_desc *desc; + + tfm = crypto_alloc_shash(alg, 0, 0); + if (IS_ERR(tfm)) + return ERR_CAST(tfm); + + desc = kzalloc(sizeof(*desc) + crypto_shash_descsize(tfm), GFP_KERNEL); + if (!desc) { + crypto_free_shash(tfm); + return ERR_PTR(-ENOMEM); + } + + desc->tfm = tfm; + crypto_shash_init(desc); + + return desc; +} + +int gip_auth_get_transcript(struct shash_desc *desc, void *transcript) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 12, 0) + struct sha256_state state; + int err; + + err = crypto_shash_export(desc, &state); + if (err) + return err; + + err = crypto_shash_final(desc, transcript); + if (err) + return err; + + return crypto_shash_import(desc, &state); +#else + struct sha256_state *state; + int err; + + state = kzalloc(crypto_shash_descsize(desc->tfm), GFP_KERNEL); + if (!state) + return err; + + err = crypto_shash_export(desc, state); + if (err) + goto err_free_state; + + err = crypto_shash_final(desc, transcript); + if (err) + goto err_free_state; + + err = crypto_shash_import(desc, state); + +err_free_state: + kfree(state); + + return err; +#endif +} + +int gip_auth_compute_prf(struct shash_desc *desc, const char *label, + u8 *key, int key_len, + u8 *seed, int seed_len, + u8 *out, int out_len) +{ + u8 hash[SHA256_DIGEST_SIZE], hash_out[SHA256_DIGEST_SIZE]; + int err; + + err = crypto_shash_setkey(desc->tfm, key, key_len); + if (err) + return err; + + crypto_shash_init(desc); + crypto_shash_update(desc, label, strlen(label)); + crypto_shash_update(desc, seed, seed_len); + crypto_shash_final(desc, hash); + + while (out_len > 0) { + crypto_shash_init(desc); + crypto_shash_update(desc, hash, sizeof(hash)); + crypto_shash_update(desc, label, strlen(label)); + crypto_shash_update(desc, seed, seed_len); + crypto_shash_final(desc, hash_out); + + memcpy(out, hash_out, min_t(int, out_len, sizeof(hash))); + out += sizeof(hash); + out_len -= sizeof(hash); + + crypto_shash_digest(desc, hash, sizeof(hash), hash); + } + + return 0; +} + +int gip_auth_encrypt_rsa(u8 *key, int key_len, + u8 *in, int in_len, + u8 *out, int out_len) +{ + struct crypto_akcipher *tfm; + struct akcipher_request *req; + struct scatterlist src, dest; + DECLARE_CRYPTO_WAIT(wait); + u8 *buf; + int err; + + buf = kzalloc(out_len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + tfm = crypto_alloc_akcipher("pkcs1pad(rsa)", 0, 0); + if (IS_ERR(tfm)) { + err = PTR_ERR(tfm); + goto err_free_buf; + } + + err = crypto_akcipher_set_pub_key(tfm, key, key_len); + if (err) + goto err_free_tfm; + + req = akcipher_request_alloc(tfm, GFP_KERNEL); + if (!req) { + err = -ENOMEM; + goto err_free_tfm; + } + + sg_init_one(&src, in, in_len); + sg_init_one(&dest, buf, out_len); + + akcipher_request_set_crypt(req, &src, &dest, in_len, out_len); + akcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG, + crypto_req_done, &wait); + err = crypto_wait_req(crypto_akcipher_encrypt(req), &wait); + if (!err) + memcpy(out, buf, out_len); + + akcipher_request_free(req); + +err_free_tfm: + crypto_free_akcipher(tfm); +err_free_buf: + kfree(buf); + + return err; +} + +static int gip_auth_ecdh_get_pubkey(struct crypto_kpp *tfm, + u8 *out, int len) +{ + struct kpp_request *req; + struct scatterlist dest; + struct ecdh key = {}; + DECLARE_CRYPTO_WAIT(wait); + void *privkey, *pubkey; + unsigned int privkey_len; + int err; + + privkey_len = crypto_ecdh_key_len(&key); + privkey = kzalloc(privkey_len, GFP_KERNEL); + if (!privkey) + return -ENOMEM; + + pubkey = kzalloc(len, GFP_KERNEL); + if (!pubkey) + goto err_free_privkey; + + /* generate private key */ + err = crypto_ecdh_encode_key(privkey, privkey_len, &key); + if (err) + goto err_free_pubkey; + + err = crypto_kpp_set_secret(tfm, privkey, privkey_len); + if (err) + goto err_free_pubkey; + + req = kpp_request_alloc(tfm, GFP_KERNEL); + if (!req) { + err = -ENOMEM; + goto err_free_pubkey; + } + + sg_init_one(&dest, pubkey, len); + + kpp_request_set_input(req, NULL, 0); + kpp_request_set_output(req, &dest, len); + kpp_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG, + crypto_req_done, &wait); + err = crypto_wait_req(crypto_kpp_generate_public_key(req), &wait); + if (!err) + memcpy(out, pubkey, len); + + kpp_request_free(req); + +err_free_pubkey: + kfree(pubkey); +err_free_privkey: + kfree(privkey); + + return err; +} + +static int gip_auth_ecdh_get_secret(struct crypto_kpp *tfm, + u8 *pubkey, int pubkey_len, + u8 *secret, int secret_len) +{ + struct kpp_request *req; + struct scatterlist src, dest; + DECLARE_CRYPTO_WAIT(wait); + int err; + + req = kpp_request_alloc(tfm, GFP_KERNEL); + if (!req) + return -ENOMEM; + + sg_init_one(&src, pubkey, pubkey_len); + sg_init_one(&dest, secret, secret_len); + + kpp_request_set_input(req, &src, pubkey_len); + kpp_request_set_output(req, &dest, secret_len); + kpp_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG, + crypto_req_done, &wait); + err = crypto_wait_req(crypto_kpp_compute_shared_secret(req), &wait); + + kpp_request_free(req); + + return err; +} + +int gip_auth_compute_ecdh(u8 *pubkey_in, u8 *pubkey_out, + int pubkey_len, u8 *secret_hash) +{ + struct crypto_kpp *tfm_ecdh; + struct crypto_shash *tfm_sha; + u8 *secret; + int err; + + secret = kzalloc(GIP_AUTH_ECDH_SECRET_LEN, GFP_KERNEL); + if (!secret) + return -ENOMEM; + + tfm_ecdh = crypto_alloc_kpp("ecdh-nist-p256", 0, 0); + if (IS_ERR(tfm_ecdh)) { + err = PTR_ERR(tfm_ecdh); + goto err_free_secret; + } + + tfm_sha = crypto_alloc_shash("sha256", 0, 0); + if (IS_ERR(tfm_sha)) { + err = PTR_ERR(tfm_sha); + goto err_free_ecdh; + } + + err = gip_auth_ecdh_get_pubkey(tfm_ecdh, pubkey_out, pubkey_len); + if (err) + goto err_free_sha; + + err = gip_auth_ecdh_get_secret(tfm_ecdh, pubkey_in, pubkey_len, + secret, GIP_AUTH_ECDH_SECRET_LEN); + if (err) + goto err_free_sha; + + crypto_shash_tfm_digest(tfm_sha, secret, GIP_AUTH_ECDH_SECRET_LEN, + secret_hash); + +err_free_sha: + crypto_free_shash(tfm_sha); +err_free_ecdh: + crypto_free_kpp(tfm_ecdh); +err_free_secret: + kfree(secret); + + return err; +} diff --git a/drivers/hid/xone/auth/crypto.h b/drivers/hid/xone/auth/crypto.h new file mode 100644 index 000000000..a34972e35 --- /dev/null +++ b/drivers/hid/xone/auth/crypto.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2023 Severin von Wnuck-Lipinski + */ + +#pragma once + +#include + +struct shash_desc *gip_auth_alloc_shash(const char *alg); +int gip_auth_get_transcript(struct shash_desc *desc, void *transcript); +int gip_auth_compute_prf(struct shash_desc *desc, const char *label, + u8 *key, int key_len, + u8 *seed, int seed_len, + u8 *out, int out_len); + +int gip_auth_encrypt_rsa(u8 *key, int key_len, + u8 *in, int in_len, + u8 *out, int out_len); +int gip_auth_compute_ecdh(u8 *pubkey_in, u8 *pubkey_out, + int pubkey_len, u8 *secret_hash); diff --git a/drivers/hid/xone/bus/bus.c b/drivers/hid/xone/bus/bus.c index c6bb21b89..32fc66890 100644 --- a/drivers/hid/xone/bus/bus.c +++ b/drivers/hid/xone/bus/bus.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2021 Severin von Wnuck + * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #include @@ -25,49 +25,12 @@ static struct device_type gip_adapter_type = { .release = gip_adapter_release, }; -static void gip_add_client(struct gip_client *client) -{ - int err; - - err = device_add(&client->dev); - if (err) { - dev_err(&client->dev, "%s: add device failed: %d\n", - __func__, err); - return; - } - - dev_dbg(&client->dev, "%s: added\n", __func__); -} - -static void gip_remove_client(struct gip_client *client) -{ - dev_dbg(&client->dev, "%s: removed\n", __func__); - - if (device_is_registered(&client->dev)) - device_del(&client->dev); - - put_device(&client->dev); -} - -static void gip_client_state_changed(struct work_struct *work) -{ - struct gip_client *client = container_of(work, typeof(*client), - state_work); - - switch (atomic_read(&client->state)) { - case GIP_CL_IDENTIFIED: - gip_add_client(client); - break; - case GIP_CL_DISCONNECTED: - gip_remove_client(client); - break; - default: - dev_warn(&client->dev, "%s: invalid state\n", __func__); - break; - } -} - +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 3, 0) static int gip_client_uevent(struct device *dev, struct kobj_uevent_env *env) +#else +static int gip_client_uevent(const struct device *dev, + struct kobj_uevent_env *env) +#endif { struct gip_client *client = to_gip_client(dev); struct gip_classes *classes = client->classes; @@ -83,7 +46,8 @@ static void gip_client_release(struct device *dev) struct gip_client *client = to_gip_client(dev); gip_free_client_info(client); - kfree(client->chunk_buf); + kfree(client->chunk_buf_out); + kfree(client->chunk_buf_in); kfree(client); } @@ -92,7 +56,11 @@ static struct device_type gip_client_type = { .release = gip_client_release, }; +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0) static int gip_bus_match(struct device *dev, struct device_driver *driver) +#else +static int gip_bus_match(struct device *dev, const struct device_driver *driver) +#endif { struct gip_client *client; struct gip_driver *drv; @@ -115,36 +83,37 @@ static int gip_bus_probe(struct device *dev) { struct gip_client *client = to_gip_client(dev); struct gip_driver *drv = to_gip_driver(dev->driver); - int err; - unsigned long flags; + int err = 0; - if (client->drv) - return 0; + if (down_interruptible(&client->drv_lock)) + return -EINTR; - err = drv->probe(client); - if (!err) { - spin_lock_irqsave(&client->lock, flags); - client->drv = drv; - spin_unlock_irqrestore(&client->lock, flags); + if (!client->drv) { + err = drv->probe(client); + if (!err) + client->drv = drv; } + up(&client->drv_lock); + return err; } static void gip_bus_remove(struct device *dev) { struct gip_client *client = to_gip_client(dev); - struct gip_driver *drv = client->drv; - unsigned long flags; + struct gip_driver *drv; - if (!drv) - return; + down(&client->drv_lock); - spin_lock_irqsave(&client->lock, flags); - client->drv = NULL; - spin_unlock_irqrestore(&client->lock, flags); + drv = client->drv; + if (drv) { + client->drv = NULL; + if (drv->remove) + drv->remove(client); + } - drv->remove(client); + up(&client->drv_lock); } #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0) @@ -178,14 +147,14 @@ struct gip_adapter *gip_create_adapter(struct device *parent, if (!adap) return ERR_PTR(-ENOMEM); - adap->id = ida_simple_get(&gip_adapter_ida, 0, 0, GFP_KERNEL); + adap->id = ida_alloc(&gip_adapter_ida, GFP_KERNEL); if (adap->id < 0) { err = adap->id; goto err_put_device; } - adap->state_queue = alloc_ordered_workqueue("gip%d", 0, adap->id); - if (!adap->state_queue) { + adap->clients_wq = alloc_ordered_workqueue("gip%d", 0, adap->id); + if (!adap->clients_wq) { err = -ENOMEM; goto err_remove_ida; } @@ -196,7 +165,6 @@ struct gip_adapter *gip_create_adapter(struct device *parent, adap->ops = ops; adap->audio_packet_count = audio_pkts; dev_set_name(&adap->dev, "gip%d", adap->id); - spin_lock_init(&adap->clients_lock); spin_lock_init(&adap->send_lock); err = device_register(&adap->dev); @@ -208,9 +176,9 @@ struct gip_adapter *gip_create_adapter(struct device *parent, return adap; err_destroy_queue: - destroy_workqueue(adap->state_queue); + destroy_workqueue(adap->clients_wq); err_remove_ida: - ida_simple_remove(&gip_adapter_ida, adap->id); + ida_free(&gip_adapter_ida, adap->id); err_put_device: put_device(&adap->dev); @@ -236,102 +204,99 @@ void gip_destroy_adapter(struct gip_adapter *adap) int i; /* ensure all state changes have been processed */ - flush_workqueue(adap->state_queue); + flush_workqueue(adap->clients_wq); for (i = GIP_MAX_CLIENTS - 1; i >= 0; i--) { client = adap->clients[i]; - if (!client) + if (!client || !device_is_registered(&client->dev)) continue; - gip_remove_client(client); - adap->clients[i] = NULL; + device_unregister(&client->dev); } - ida_simple_remove(&gip_adapter_ida, adap->id); - destroy_workqueue(adap->state_queue); + ida_free(&gip_adapter_ida, adap->id); + destroy_workqueue(adap->clients_wq); dev_dbg(&adap->dev, "%s: unregistered\n", __func__); device_unregister(&adap->dev); } EXPORT_SYMBOL_GPL(gip_destroy_adapter); -static struct gip_client *gip_init_client(struct gip_adapter *adap, u8 id) +static void gip_register_client(struct work_struct *work) +{ + struct gip_client *client = container_of(work, typeof(*client), + work_register); + int err; + + client->dev.parent = &client->adapter->dev; + client->dev.type = &gip_client_type; + client->dev.bus = &gip_bus_type; + sema_init(&client->drv_lock, 1); + dev_set_name(&client->dev, "gip%d.%u", client->adapter->id, client->id); + + err = device_register(&client->dev); + if (err) + dev_err(&client->dev, "%s: register failed: %d\n", + __func__, err); + else + dev_dbg(&client->dev, "%s: registered\n", __func__); +} + +static void gip_unregister_client(struct work_struct *work) +{ + struct gip_client *client = container_of(work, typeof(*client), + work_unregister); + + if (!device_is_registered(&client->dev)) + return; + + dev_dbg(&client->dev, "%s: unregistered\n", __func__); + device_unregister(&client->dev); +} + +struct gip_client *gip_get_client(struct gip_adapter *adap, u8 id) { struct gip_client *client; + client = adap->clients[id]; + if (client) + return client; + client = kzalloc(sizeof(*client), GFP_ATOMIC); if (!client) return ERR_PTR(-ENOMEM); - client->dev.parent = &adap->dev; - client->dev.type = &gip_client_type; - client->dev.bus = &gip_bus_type; client->id = id; client->adapter = adap; - dev_set_name(&client->dev, "gip%d.%u", adap->id, client->id); - atomic_set(&client->state, GIP_CL_CONNECTED); - spin_lock_init(&client->lock); - INIT_WORK(&client->state_work, gip_client_state_changed); + sema_init(&client->drv_lock, 1); + INIT_WORK(&client->work_register, gip_register_client); + INIT_WORK(&client->work_unregister, gip_unregister_client); - device_initialize(&client->dev); - dev_dbg(&client->dev, "%s: initialized\n", __func__); + adap->clients[id] = client; + + dev_dbg(&client->adapter->dev, "%s: initialized client %u\n", + __func__, id); return client; } -struct gip_client *gip_get_or_init_client(struct gip_adapter *adap, u8 id) +void gip_add_client(struct gip_client *client) { - struct gip_client *client; - unsigned long flags; - - spin_lock_irqsave(&adap->clients_lock, flags); - - client = adap->clients[id]; - if (!client) { - client = gip_init_client(adap, id); - if (IS_ERR(client)) - goto err_unlock; - - adap->clients[id] = client; - } - - get_device(&client->dev); - -err_unlock: - spin_unlock_irqrestore(&adap->clients_lock, flags); - - return client; + queue_work(client->adapter->clients_wq, &client->work_register); } -void gip_put_client(struct gip_client *client) +void gip_remove_client(struct gip_client *client) { - put_device(&client->dev); -} - -void gip_register_client(struct gip_client *client) -{ - atomic_set(&client->state, GIP_CL_IDENTIFIED); - queue_work(client->adapter->state_queue, &client->state_work); -} - -void gip_unregister_client(struct gip_client *client) -{ - struct gip_adapter *adap = client->adapter; - unsigned long flags; - - spin_lock_irqsave(&adap->clients_lock, flags); - adap->clients[client->id] = NULL; - spin_unlock_irqrestore(&adap->clients_lock, flags); - - atomic_set(&client->state, GIP_CL_DISCONNECTED); - queue_work(adap->state_queue, &client->state_work); + client->adapter->clients[client->id] = NULL; + queue_work(client->adapter->clients_wq, &client->work_unregister); } void gip_free_client_info(struct gip_client *client) { int i; - kfree(client->external_commands); + kfree(client->client_commands); + kfree(client->firmware_versions); kfree(client->audio_formats); kfree(client->capabilities_out); kfree(client->capabilities_in); @@ -344,7 +309,7 @@ void gip_free_client_info(struct gip_client *client) kfree(client->interfaces); kfree(client->hid_descriptor); - client->external_commands = NULL; + client->client_commands = NULL; client->audio_formats = NULL; client->capabilities_out = NULL; client->capabilities_in = NULL; @@ -384,6 +349,7 @@ static void __exit gip_bus_exit(void) module_init(gip_bus_init); module_exit(gip_bus_exit); -MODULE_AUTHOR("Severin von Wnuck "); -MODULE_DESCRIPTION("xone GIP bus driver"); +MODULE_AUTHOR("Severin von Wnuck-Lipinski "); +MODULE_DESCRIPTION("xone GIP driver"); +MODULE_VERSION("#VERSION#"); MODULE_LICENSE("GPL"); diff --git a/drivers/hid/xone/bus/bus.h b/drivers/hid/xone/bus/bus.h index 09875f720..c107e1e3f 100644 --- a/drivers/hid/xone/bus/bus.h +++ b/drivers/hid/xone/bus/bus.h @@ -1,12 +1,13 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ /* - * Copyright (C) 2021 Severin von Wnuck + * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #pragma once #include #include +#include #include "protocol.h" @@ -34,6 +35,7 @@ struct gip_adapter_ops { struct gip_adapter_buffer *buf); int (*submit_buffer)(struct gip_adapter *adap, struct gip_adapter_buffer *buf); + int (*set_encryption_key)(struct gip_adapter *adap, u8 *key, int len); int (*enable_audio)(struct gip_adapter *adap); int (*init_audio_in)(struct gip_adapter *adap); int (*init_audio_out)(struct gip_adapter *adap, int pkt_len); @@ -48,10 +50,7 @@ struct gip_adapter { int audio_packet_count; struct gip_client *clients[GIP_MAX_CLIENTS]; - struct workqueue_struct *state_queue; - - /* serializes access to clients array */ - spinlock_t clients_lock; + struct workqueue_struct *clients_wq; /* serializes access to data sequence number */ spinlock_t send_lock; @@ -63,40 +62,42 @@ struct gip_adapter { struct gip_client { struct device dev; u8 id; - atomic_t state; struct gip_adapter *adapter; struct gip_driver *drv; + struct semaphore drv_lock; - struct gip_chunk_buffer *chunk_buf; + struct work_struct work_register; + struct work_struct work_unregister; + + struct gip_chunk_buffer *chunk_buf_out; + struct gip_chunk_buffer *chunk_buf_in; struct gip_hardware hardware; - struct gip_info_element *external_commands; + struct gip_info_element *client_commands; + struct gip_info_element *firmware_versions; struct gip_info_element *audio_formats; - struct gip_info_element *capabilities_in; struct gip_info_element *capabilities_out; + struct gip_info_element *capabilities_in; struct gip_classes *classes; struct gip_info_element *interfaces; struct gip_info_element *hid_descriptor; struct gip_audio_config audio_config_in; struct gip_audio_config audio_config_out; - - /* serializes packet processing */ - spinlock_t lock; - struct work_struct state_work; }; struct gip_driver_ops { int (*battery)(struct gip_client *client, enum gip_battery_type type, enum gip_battery_level level); + int (*authenticate)(struct gip_client *client, void *data, u32 len); int (*guide_button)(struct gip_client *client, bool down); int (*audio_ready)(struct gip_client *client); - int (*audio_volume)(struct gip_client *client, int in, int out); - int (*hid_report)(struct gip_client *client, void *data, int len); - int (*input)(struct gip_client *client, void *data, int len); - int (*audio_samples)(struct gip_client *client, void *data, int len); + int (*audio_volume)(struct gip_client *client, u8 in, u8 out); + int (*hid_report)(struct gip_client *client, void *data, u32 len); + int (*input)(struct gip_client *client, void *data, u32 len); + int (*audio_samples)(struct gip_client *client, void *data, u32 len); }; struct gip_driver { @@ -116,10 +117,9 @@ struct gip_adapter *gip_create_adapter(struct device *parent, int gip_power_off_adapter(struct gip_adapter *adap); void gip_destroy_adapter(struct gip_adapter *adap); -struct gip_client *gip_get_or_init_client(struct gip_adapter *adap, u8 id); -void gip_put_client(struct gip_client *client); -void gip_register_client(struct gip_client *client); -void gip_unregister_client(struct gip_client *client); +struct gip_client *gip_get_client(struct gip_adapter *adap, u8 id); +void gip_add_client(struct gip_client *client); +void gip_remove_client(struct gip_client *client); void gip_free_client_info(struct gip_client *client); int __gip_register_driver(struct gip_driver *drv, struct module *owner, diff --git a/drivers/hid/xone/bus/protocol.c b/drivers/hid/xone/bus/protocol.c index 2724e7083..1f24a0d22 100644 --- a/drivers/hid/xone/bus/protocol.c +++ b/drivers/hid/xone/bus/protocol.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2021 Severin von Wnuck + * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #include @@ -9,13 +9,12 @@ #include "bus.h" -/* vendor/product ID for the chat headset */ -#define GIP_VID_MICROSOFT 0x045e -#define GIP_PID_CHAT_HEADSET 0x0111 - #define GIP_HDR_CLIENT_ID GENMASK(3, 0) #define GIP_HDR_MIN_LENGTH 3 +/* max length, even for wireless packets (except audio) */ +#define GIP_PKT_MAX_LENGTH 58 + #define GIP_CHUNK_BUF_MAX_LENGTH 0xffff #define GIP_BATT_LEVEL GENMASK(1, 0) @@ -24,7 +23,11 @@ #define GIP_VKEY_LEFT_WIN 0x5b -enum gip_command_internal { +#define gip_dbg(client, ...) dev_dbg(&(client)->adapter->dev, __VA_ARGS__) +#define gip_warn(client, ...) dev_warn(&(client)->adapter->dev, __VA_ARGS__) +#define gip_err(client, ...) dev_err(&(client)->adapter->dev, __VA_ARGS__) + +enum gip_command_core { GIP_CMD_ACKNOWLEDGE = 0x01, GIP_CMD_ANNOUNCE = 0x02, GIP_CMD_STATUS = 0x03, @@ -40,7 +43,7 @@ enum gip_command_internal { GIP_CMD_AUDIO_SAMPLES = 0x60, }; -enum gip_command_external { +enum gip_command_client { GIP_CMD_RUMBLE = 0x09, GIP_CMD_INPUT = 0x20, }; @@ -64,14 +67,6 @@ enum gip_audio_volume_mute { GIP_AUD_VOLUME_MIC_MUTED = 0x05, }; -struct gip_header { - u8 command; - u8 options; - u8 sequence; - u32 packet_length; - u32 chunk_offset; -}; - struct gip_pkt_acknowledge { u8 unknown; u8 command; @@ -101,8 +96,8 @@ struct gip_pkt_status { struct gip_pkt_identify { u8 unknown[16]; - __le16 external_commands_offset; - __le16 unknown_offset; + __le16 client_commands_offset; + __le16 firmware_versions_offset; __le16 audio_formats_offset; __le16 capabilities_out_offset; __le16 capabilities_in_offset; @@ -115,11 +110,6 @@ struct gip_pkt_power { u8 mode; } __packed; -struct gip_pkt_authenticate { - u8 unknown1; - u8 unknown2; -} __packed; - struct gip_pkt_virtual_key { u8 down; u8 key; @@ -152,10 +142,10 @@ struct gip_pkt_audio_volume { struct gip_pkt_audio_control control; u8 mute; u8 out; - u8 unknown1; + u8 chat; u8 in; - u8 unknown2; - u8 unknown3[2]; + u8 unknown1; + u8 unknown2[2]; } __packed; struct gip_pkt_led { @@ -184,6 +174,11 @@ struct gip_command_descriptor { u8 unknown3[15]; } __packed; +struct gip_firmware_version { + __le16 major; + __le16 minor; +} __packed; + static int gip_encode_varint(u8 *buf, u32 val) { int i; @@ -217,7 +212,7 @@ static int gip_decode_varint(u8 *data, int len, u32 *val) return i + 1; } -static int gip_calc_header_space(struct gip_header *hdr) +static int gip_get_actual_header_length(struct gip_header *hdr) { u32 pkt_len = hdr->packet_length; u32 chunk_offset = hdr->chunk_offset; @@ -238,9 +233,9 @@ static int gip_calc_header_space(struct gip_header *hdr) return len; } -static int gip_calc_header_length(struct gip_header *hdr) +static int gip_get_header_length(struct gip_header *hdr) { - int len = gip_calc_header_space(hdr); + int len = gip_get_actual_header_length(hdr); /* round up to nearest even length */ return len + (len % 2); @@ -257,7 +252,7 @@ static void gip_encode_header(struct gip_header *hdr, u8 *buf) hdr_len += gip_encode_varint(buf + hdr_len, hdr->packet_length); /* header length must be even */ - if (gip_calc_header_space(hdr) % 2) { + if (gip_get_actual_header_length(hdr) % 2) { buf[hdr_len - 1] |= BIT(7); buf[hdr_len++] = 0; } @@ -286,8 +281,35 @@ static int gip_decode_header(struct gip_header *hdr, u8 *data, int len) return hdr_len; } -static int gip_send_pkt(struct gip_client *client, - struct gip_header *hdr, void *data) +static int gip_init_chunk_buffer(struct gip_client *client, + struct gip_header *hdr, + struct gip_chunk_buffer **buf) +{ + /* offset is total length of all chunks */ + if (hdr->chunk_offset > GIP_CHUNK_BUF_MAX_LENGTH) + return -EINVAL; + + if (*buf) { + gip_err(client, "%s: already initialized\n", __func__); + kfree(*buf); + *buf = NULL; + } + + *buf = kzalloc(sizeof(**buf) + hdr->chunk_offset, GFP_ATOMIC); + if (!*buf) + return -ENOMEM; + + (*buf)->header = *hdr; + (*buf)->header.options &= ~(GIP_OPT_ACKNOWLEDGE | GIP_OPT_CHUNK_START); + (*buf)->length = hdr->chunk_offset; + gip_dbg(client, "%s: command=0x%02x, length=0x%04x\n", __func__, + (*buf)->header.command, (*buf)->length); + + return 0; +} + +static int gip_send_pkt_simple(struct gip_client *client, + struct gip_header *hdr, void *data) { struct gip_adapter *adap = client->adapter; struct gip_adapter_buffer buf = {}; @@ -300,12 +322,11 @@ static int gip_send_pkt(struct gip_client *client, err = adap->ops->get_buffer(adap, &buf); if (err) { - dev_err(&client->dev, "%s: get buffer failed: %d\n", - __func__, err); + gip_err(client, "%s: get buffer failed: %d\n", __func__, err); goto err_unlock; } - hdr_len = gip_calc_header_length(hdr); + hdr_len = gip_get_header_length(hdr); if (buf.length < hdr_len + hdr->packet_length) { err = -ENOSPC; goto err_unlock; @@ -325,7 +346,7 @@ static int gip_send_pkt(struct gip_client *client, /* always fails on adapter removal */ err = adap->ops->submit_buffer(adap, &buf); if (err) - dev_dbg(&client->dev, "%s: submit buffer failed: %d\n", + gip_dbg(client, "%s: submit buffer failed: %d\n", __func__, err); err_unlock: @@ -334,12 +355,42 @@ err_unlock: return err; } +static int gip_send_pkt(struct gip_client *client, + struct gip_header *hdr, void *data) +{ + int err; + + /* packet fits into single buffer */ + if (hdr->packet_length <= GIP_PKT_MAX_LENGTH) + return gip_send_pkt_simple(client, hdr, data); + + /* chunk offset of first chunk is total length */ + hdr->options |= GIP_OPT_ACKNOWLEDGE | GIP_OPT_CHUNK_START | + GIP_OPT_CHUNK; + hdr->chunk_offset = hdr->packet_length; + hdr->packet_length = GIP_PKT_MAX_LENGTH; + + err = gip_send_pkt_simple(client, hdr, data); + if (err) + return err; + + /* allocate buffer for all chunks */ + err = gip_init_chunk_buffer(client, hdr, &client->chunk_buf_in); + if (err) + return err; + + memcpy(client->chunk_buf_in->data, data, hdr->chunk_offset); + + return 0; +} + static int gip_acknowledge_pkt(struct gip_client *client, struct gip_header *ack) { - struct gip_chunk_buffer *chunk_buf = client->chunk_buf; + struct gip_chunk_buffer *buf = client->chunk_buf_out; struct gip_header hdr = {}; struct gip_pkt_acknowledge pkt = {}; + u32 len = ack->chunk_offset + ack->packet_length; hdr.command = GIP_CMD_ACKNOWLEDGE; hdr.options = client->id | GIP_OPT_INTERNAL; @@ -348,10 +399,10 @@ static int gip_acknowledge_pkt(struct gip_client *client, pkt.command = ack->command; pkt.options = client->id | GIP_OPT_INTERNAL; - pkt.length = cpu_to_le16(ack->chunk_offset + ack->packet_length); + pkt.length = cpu_to_le16(len); - if ((ack->options & GIP_OPT_CHUNK) && chunk_buf) - pkt.remaining = cpu_to_le16(chunk_buf->length) - pkt.length; + if ((ack->options & GIP_OPT_CHUNK) && buf) + pkt.remaining = cpu_to_le16(buf->length - len); return gip_send_pkt(client, &hdr, &pkt); } @@ -381,20 +432,20 @@ int gip_set_power_mode(struct gip_client *client, enum gip_power_mode mode) } EXPORT_SYMBOL_GPL(gip_set_power_mode); -int gip_complete_authentication(struct gip_client *client) +int gip_send_authenticate(struct gip_client *client, void *pkt, u32 len, + bool acknowledge) { struct gip_header hdr = {}; - struct gip_pkt_authenticate pkt = {}; hdr.command = GIP_CMD_AUTHENTICATE; hdr.options = client->id | GIP_OPT_INTERNAL; - hdr.packet_length = sizeof(pkt); + hdr.packet_length = len; - pkt.unknown1 = 0x01; + if (acknowledge) + hdr.options |= GIP_OPT_ACKNOWLEDGE; - return gip_send_pkt(client, &hdr, &pkt); + return gip_send_pkt(client, &hdr, pkt); } -EXPORT_SYMBOL_GPL(gip_complete_authentication); static int gip_set_audio_format_chat(struct gip_client *client, enum gip_audio_format_chat in_out) @@ -432,22 +483,20 @@ static int gip_set_audio_format(struct gip_client *client, int gip_suggest_audio_format(struct gip_client *client, enum gip_audio_format in, - enum gip_audio_format out) + enum gip_audio_format out, + bool chat) { - struct gip_hardware *hw = &client->hardware; int err; /* special handling for the chat headset */ - if (hw->vendor == GIP_VID_MICROSOFT && - hw->product == GIP_PID_CHAT_HEADSET) + if (chat) err = gip_set_audio_format_chat(client, GIP_AUD_FORMAT_CHAT_24KHZ); else err = gip_set_audio_format(client, in, out); if (err) { - dev_err(&client->dev, "%s: set format failed: %d\n", - __func__, err); + gip_err(client, "%s: set format failed: %d\n", __func__, err); return err; } @@ -458,7 +507,7 @@ int gip_suggest_audio_format(struct gip_client *client, } EXPORT_SYMBOL_GPL(gip_suggest_audio_format); -static int gip_set_audio_volume(struct gip_client *client, u8 in, u8 out) +int gip_set_audio_volume(struct gip_client *client, u8 in, u8 chat, u8 out) { struct gip_header hdr = {}; struct gip_pkt_audio_volume pkt = {}; @@ -470,24 +519,12 @@ static int gip_set_audio_volume(struct gip_client *client, u8 in, u8 out) pkt.control.subcommand = GIP_AUD_CTRL_VOLUME; pkt.mute = GIP_AUD_VOLUME_UNMUTED; pkt.out = out; + pkt.chat = chat; pkt.in = in; return gip_send_pkt(client, &hdr, &pkt); } - -int gip_fix_audio_volume(struct gip_client *client) -{ - struct gip_hardware *hw = &client->hardware; - - /* chat headsets have buttons to adjust the hardware volume */ - if (hw->vendor == GIP_VID_MICROSOFT && - hw->product == GIP_PID_CHAT_HEADSET) - return 0; - - /* set hardware volume to maximum */ - return gip_set_audio_volume(client, 100, 100); -} -EXPORT_SYMBOL_GPL(gip_fix_audio_volume); +EXPORT_SYMBOL_GPL(gip_set_audio_volume); int gip_send_rumble(struct gip_client *client, void *pkt, u32 len) { @@ -530,7 +567,7 @@ static void gip_copy_audio_samples(struct gip_client *client, hdr.options = client->id | GIP_OPT_INTERNAL; hdr.packet_length = cfg->fragment_size; - hdr_len = gip_calc_header_length(&hdr); + hdr_len = gip_get_header_length(&hdr); for (i = 0; i < client->adapter->audio_packet_count; i++) { src = samples + i * cfg->fragment_size; @@ -557,8 +594,7 @@ int gip_send_audio_samples(struct gip_client *client, void *samples) /* returns ENOSPC if no buffer is available */ err = adap->ops->get_buffer(adap, &buf); if (err) { - dev_err(&client->dev, "%s: get buffer failed: %d\n", - __func__, err); + gip_err(client, "%s: get buffer failed: %d\n", __func__, err); return err; } @@ -571,13 +607,41 @@ int gip_send_audio_samples(struct gip_client *client, void *samples) /* always fails on adapter removal */ err = adap->ops->submit_buffer(adap, &buf); if (err) - dev_dbg(&client->dev, "%s: submit buffer failed: %d\n", + gip_dbg(client, "%s: submit buffer failed: %d\n", __func__, err); return err; } EXPORT_SYMBOL_GPL(gip_send_audio_samples); +bool gip_has_interface(struct gip_client *client, const guid_t *guid) +{ + int i; + + for (i = 0; i < client->interfaces->count; i++) { + if (guid_equal((guid_t *)client->interfaces->data + i, guid)) + return true; + } + + return false; +} +EXPORT_SYMBOL_GPL(gip_has_interface); + +int gip_set_encryption_key(struct gip_client *client, u8 *key, int len) +{ + struct gip_adapter *adap = client->adapter; + int err; + + if (!adap->ops->set_encryption_key) + return 0; + + err = adap->ops->set_encryption_key(adap, key, len); + if (err) + gip_err(client, "%s: set key failed: %d\n", __func__, err); + + return err; +} + int gip_enable_audio(struct gip_client *client) { struct gip_adapter *adap = client->adapter; @@ -588,8 +652,7 @@ int gip_enable_audio(struct gip_client *client) err = adap->ops->enable_audio(adap); if (err) - dev_err(&client->dev, "%s: enable failed: %d\n", - __func__, err); + gip_err(client, "%s: enable failed: %d\n", __func__, err); return err; } @@ -605,7 +668,7 @@ int gip_init_audio_in(struct gip_client *client) err = adap->ops->init_audio_in(adap); if (err) - dev_err(&client->dev, "%s: init failed: %d\n", __func__, err); + gip_err(client, "%s: init failed: %d\n", __func__, err); return err; } @@ -622,7 +685,7 @@ int gip_init_audio_out(struct gip_client *client) err = adap->ops->init_audio_out(adap, client->audio_config_out.packet_size); if (err) - dev_err(&client->dev, "%s: init failed: %d\n", __func__, err); + gip_err(client, "%s: init failed: %d\n", __func__, err); return err; } @@ -639,8 +702,7 @@ void gip_disable_audio(struct gip_client *client) /* always fails on adapter removal */ err = adap->ops->disable_audio(adap); if (err) - dev_dbg(&client->dev, "%s: disable failed: %d\n", - __func__, err); + gip_dbg(client, "%s: disable failed: %d\n", __func__, err); } EXPORT_SYMBOL_GPL(gip_disable_audio); @@ -650,6 +712,10 @@ static int gip_make_audio_config(struct gip_client *client, struct gip_header hdr = {}; switch (cfg->format) { + case GIP_AUD_FORMAT_16KHZ_MONO: + cfg->channels = 1; + cfg->sample_rate = 16000; + break; case GIP_AUD_FORMAT_24KHZ_MONO: cfg->channels = 1; cfg->sample_rate = 24000; @@ -659,7 +725,7 @@ static int gip_make_audio_config(struct gip_client *client, cfg->sample_rate = 48000; break; default: - dev_err(&client->dev, "%s: unknown format: 0x%02x\n", + gip_err(client, "%s: unknown format: 0x%02x\n", __func__, cfg->format); return -ENOTSUPP; } @@ -671,10 +737,9 @@ static int gip_make_audio_config(struct gip_client *client, /* pseudo header for length calculation */ hdr.packet_length = cfg->fragment_size; - cfg->packet_size = gip_calc_header_length(&hdr) + cfg->fragment_size; - cfg->valid = true; + cfg->packet_size = gip_get_header_length(&hdr) + cfg->fragment_size; - dev_dbg(&client->dev, "%s: rate=%d/%d, buffer=%d\n", __func__, + gip_dbg(client, "%s: rate=%d/%d, buffer=%d\n", __func__, cfg->sample_rate, cfg->channels, cfg->buffer_size); return 0; @@ -713,33 +778,60 @@ static struct gip_info_element *gip_parse_info_element(u8 *data, u32 len, return elem; } -static int gip_parse_external_commands(struct gip_client *client, - struct gip_pkt_identify *pkt, - u8 *data, u32 len) +static int gip_parse_client_commands(struct gip_client *client, + struct gip_pkt_identify *pkt, + u8 *data, u32 len) { struct gip_info_element *cmds; struct gip_command_descriptor *desc; int i; - cmds = gip_parse_info_element(data, len, pkt->external_commands_offset, + cmds = gip_parse_info_element(data, len, pkt->client_commands_offset, sizeof(*desc)); if (IS_ERR(cmds)) { if (PTR_ERR(cmds) == -ENOTSUPP) return 0; - dev_err(&client->dev, "%s: parse failed: %ld\n", + gip_err(client, "%s: parse failed: %ld\n", __func__, PTR_ERR(cmds)); return PTR_ERR(cmds); } for (i = 0; i < cmds->count; i++) { desc = (struct gip_command_descriptor *)cmds->data + i; - dev_dbg(&client->dev, + gip_dbg(client, "%s: command=0x%02x, length=0x%02x, options=0x%02x\n", __func__, desc->command, desc->length, desc->options); } - client->external_commands = cmds; + client->client_commands = cmds; + + return 0; +} + +static int gip_parse_firmware_versions(struct gip_client *client, + struct gip_pkt_identify *pkt, + u8 *data, u32 len) +{ + struct gip_info_element *vers; + struct gip_firmware_version *ver; + int i; + + vers = gip_parse_info_element(data, len, pkt->firmware_versions_offset, + sizeof(*ver)); + if (IS_ERR(vers)) { + gip_err(client, "%s: parse failed: %ld\n", + __func__, PTR_ERR(vers)); + return PTR_ERR(vers); + } + + for (i = 0; i < vers->count; i++) { + ver = (struct gip_firmware_version *)vers->data + i; + gip_dbg(client, "%s: version=%u.%u\n", __func__, + le16_to_cpu(ver->major), le16_to_cpu(ver->minor)); + } + + client->firmware_versions = vers; return 0; } @@ -756,12 +848,12 @@ static int gip_parse_audio_formats(struct gip_client *client, if (PTR_ERR(fmts) == -ENOTSUPP) return 0; - dev_err(&client->dev, "%s: parse failed: %ld\n", + gip_err(client, "%s: parse failed: %ld\n", __func__, PTR_ERR(fmts)); return PTR_ERR(fmts); } - dev_dbg(&client->dev, "%s: formats=%*phD\n", __func__, + gip_dbg(client, "%s: formats=%*phD\n", __func__, fmts->count * 2, fmts->data); client->audio_formats = fmts; @@ -777,25 +869,23 @@ static int gip_parse_capabilities(struct gip_client *client, caps = gip_parse_info_element(data, len, pkt->capabilities_out_offset, 1); if (IS_ERR(caps)) { - dev_err(&client->dev, "%s: parse out failed: %ld\n", + gip_err(client, "%s: parse out failed: %ld\n", __func__, PTR_ERR(caps)); return PTR_ERR(caps); } - dev_dbg(&client->dev, "%s: out=%*phD\n", __func__, - caps->count, caps->data); + gip_dbg(client, "%s: out=%*phD\n", __func__, caps->count, caps->data); client->capabilities_out = caps; caps = gip_parse_info_element(data, len, pkt->capabilities_in_offset, 1); if (IS_ERR(caps)) { - dev_err(&client->dev, "%s: parse in failed: %ld\n", + gip_err(client, "%s: parse in failed: %ld\n", __func__, PTR_ERR(caps)); return PTR_ERR(caps); } - dev_dbg(&client->dev, "%s: in=%*phD\n", __func__, - caps->count, caps->data); + gip_dbg(client, "%s: in=%*phD\n", __func__, caps->count, caps->data); client->capabilities_in = caps; return 0; @@ -830,7 +920,7 @@ static int gip_parse_classes(struct gip_client *client, if (len < off + sizeof(str_len)) return -EINVAL; - str_len = le16_to_cpup((u16 *)(data + off)); + str_len = le16_to_cpup((__le16 *)(data + off)); off += sizeof(str_len); if (!str_len || len < off + str_len) return -EINVAL; @@ -845,7 +935,7 @@ static int gip_parse_classes(struct gip_client *client, classes->count++; off += str_len; - dev_dbg(&client->dev, "%s: class=%s\n", __func__, str); + gip_dbg(client, "%s: class=%s\n", __func__, str); } return 0; @@ -862,14 +952,14 @@ static int gip_parse_interfaces(struct gip_client *client, intfs = gip_parse_info_element(data, len, pkt->interfaces_offset, sizeof(guid_t)); if (IS_ERR(intfs)) { - dev_err(&client->dev, "%s: parse failed: %ld\n", + gip_err(client, "%s: parse failed: %ld\n", __func__, PTR_ERR(intfs)); return PTR_ERR(intfs); } for (i = 0; i < intfs->count; i++) { guid = (guid_t *)intfs->data + i; - dev_dbg(&client->dev, "%s: guid=%pUb\n", __func__, guid); + gip_dbg(client, "%s: guid=%pUb\n", __func__, guid); } client->interfaces = intfs; @@ -889,17 +979,76 @@ static int gip_parse_hid_descriptor(struct gip_client *client, if (PTR_ERR(desc) == -ENOTSUPP) return 0; - dev_err(&client->dev, "%s: parse failed: %ld\n", + gip_err(client, "%s: parse failed: %ld\n", __func__, PTR_ERR(desc)); return PTR_ERR(desc); } - dev_dbg(&client->dev, "%s: length=0x%02x\n", __func__, desc->count); + gip_dbg(client, "%s: length=0x%02x\n", __func__, desc->count); client->hid_descriptor = desc; return 0; } +static int gip_send_remaining_chunks(struct gip_client *client) +{ + struct gip_chunk_buffer *buf = client->chunk_buf_in; + struct gip_header hdr = buf->header; + u32 len = buf->length - GIP_PKT_MAX_LENGTH; + int err; + + while (len) { + /* require acknowledgment for last chunk */ + if (len <= GIP_PKT_MAX_LENGTH) + hdr.options |= GIP_OPT_ACKNOWLEDGE; + + hdr.packet_length = min_t(u32, len, GIP_PKT_MAX_LENGTH); + hdr.chunk_offset = buf->length - len; + + err = gip_send_pkt_simple(client, &hdr, + buf->data + hdr.chunk_offset); + if (err) + return err; + + len -= hdr.packet_length; + } + + return 0; +} + +static int gip_handle_pkt_acknowledge(struct gip_client *client, + void *data, u32 len) +{ + struct gip_pkt_acknowledge *pkt = data; + struct gip_chunk_buffer *buf = client->chunk_buf_in; + struct gip_header hdr; + + if (len != sizeof(*pkt)) + return -EINVAL; + + if (!buf) + return 0; + + /* acknowledgment for different command */ + if (pkt->command != buf->header.command) + return 0; + + if (le16_to_cpu(pkt->length) < buf->length) + return gip_send_remaining_chunks(client); + + gip_dbg(client, "%s: all chunks sent\n", __func__); + + /* empty chunk signals the completion of the transfer */ + hdr = buf->header; + hdr.packet_length = 0; + hdr.chunk_offset = buf->length; + + kfree(buf); + client->chunk_buf_in = NULL; + + return gip_send_pkt_simple(client, &hdr, NULL); +} + static int gip_handle_pkt_announce(struct gip_client *client, void *data, u32 len) { @@ -909,21 +1058,16 @@ static int gip_handle_pkt_announce(struct gip_client *client, if (len != sizeof(*pkt)) return -EINVAL; - if (atomic_read(&client->state) != GIP_CL_CONNECTED) { - dev_warn(&client->dev, "%s: invalid state\n", __func__); - return 0; + if (!hw->vendor && !hw->product && !hw->version) { + hw->vendor = le16_to_cpu(pkt->vendor_id); + hw->product = le16_to_cpu(pkt->product_id); + hw->version = (le16_to_cpu(pkt->fw_version.major) << 8) | + le16_to_cpu(pkt->fw_version.minor); } - hw->vendor = le16_to_cpu(pkt->vendor_id); - hw->product = le16_to_cpu(pkt->product_id); - hw->version = (le16_to_cpu(pkt->fw_version.major) << 8) | - le16_to_cpu(pkt->fw_version.minor); - - dev_dbg(&client->dev, - "%s: address=%pM, vendor=0x%04x, product=0x%04x\n", + gip_dbg(client, "%s: address=%pM, vendor=0x%04x, product=0x%04x\n", __func__, pkt->address, hw->vendor, hw->product); - dev_dbg(&client->dev, - "%s: firmware=%u.%u.%u.%u, hardware=%u.%u.%u.%u\n", + gip_dbg(client, "%s: firmware=%u.%u.%u.%u, hardware=%u.%u.%u.%u\n", __func__, le16_to_cpu(pkt->fw_version.major), le16_to_cpu(pkt->fw_version.minor), @@ -934,8 +1078,6 @@ static int gip_handle_pkt_announce(struct gip_client *client, le16_to_cpu(pkt->hw_version.build), le16_to_cpu(pkt->hw_version.revision)); - atomic_set(&client->state, GIP_CL_ANNOUNCED); - return gip_request_identification(client); } @@ -943,24 +1085,31 @@ static int gip_handle_pkt_status(struct gip_client *client, void *data, u32 len) { struct gip_pkt_status *pkt = data; + int err = 0; + u8 batt_type, batt_lvl; /* some devices occasionally send larger status packets */ if (len < sizeof(*pkt)) return -EINVAL; if (!(pkt->status & GIP_STATUS_CONNECTED)) { - /* schedule client removal */ - dev_dbg(&client->dev, "%s: disconnected\n", __func__); - gip_unregister_client(client); + gip_dbg(client, "%s: disconnected\n", __func__); + gip_remove_client(client); return 0; } - if (!client->drv || !client->drv->ops.battery) - return 0; + batt_type = FIELD_GET(GIP_BATT_TYPE, pkt->status); + batt_lvl = FIELD_GET(GIP_BATT_LEVEL, pkt->status); - return client->drv->ops.battery(client, - FIELD_GET(GIP_BATT_TYPE, pkt->status), - FIELD_GET(GIP_BATT_LEVEL, pkt->status)); + if (down_trylock(&client->drv_lock)) + return -EBUSY; + + if (client->drv && client->drv->ops.battery) + err = client->drv->ops.battery(client, batt_type, batt_lvl); + + up(&client->drv_lock); + + return err; } static int gip_handle_pkt_identify(struct gip_client *client, @@ -972,8 +1121,8 @@ static int gip_handle_pkt_identify(struct gip_client *client, if (len < sizeof(*pkt)) return -EINVAL; - if (atomic_read(&client->state) != GIP_CL_ANNOUNCED) { - dev_warn(&client->dev, "%s: invalid state\n", __func__); + if (client->classes) { + gip_warn(client, "%s: already identified\n", __func__); return 0; } @@ -981,7 +1130,11 @@ static int gip_handle_pkt_identify(struct gip_client *client, data += sizeof(pkt->unknown); len -= sizeof(pkt->unknown); - err = gip_parse_external_commands(client, pkt, data, len); + err = gip_parse_client_commands(client, pkt, data, len); + if (err) + goto err_free_info; + + err = gip_parse_firmware_versions(client, pkt, data, len); if (err) goto err_free_info; @@ -1005,8 +1158,7 @@ static int gip_handle_pkt_identify(struct gip_client *client, if (err) goto err_free_info; - /* schedule client registration */ - gip_register_client(client); + gip_add_client(client); return 0; @@ -1016,10 +1168,27 @@ err_free_info: return err; } +static int gip_handle_pkt_authenticate(struct gip_client *client, + void *data, u32 len) +{ + int err = 0; + + if (down_trylock(&client->drv_lock)) + return -EBUSY; + + if (client->drv && client->drv->ops.authenticate) + err = client->drv->ops.authenticate(client, data, len); + + up(&client->drv_lock); + + return err; +} + static int gip_handle_pkt_virtual_key(struct gip_client *client, void *data, u32 len) { struct gip_pkt_virtual_key *pkt = data; + int err = 0; if (len != sizeof(*pkt)) return -EINVAL; @@ -1027,10 +1196,15 @@ static int gip_handle_pkt_virtual_key(struct gip_client *client, if (pkt->key != GIP_VKEY_LEFT_WIN) return -EINVAL; - if (!client->drv || !client->drv->ops.guide_button) - return 0; + if (down_trylock(&client->drv_lock)) + return -EBUSY; - return client->drv->ops.guide_button(client, pkt->down); + if (client->drv && client->drv->ops.guide_button) + err = client->drv->ops.guide_button(client, pkt->down); + + up(&client->drv_lock); + + return err; } static int gip_handle_pkt_audio_format_chat(struct gip_client *client, @@ -1045,7 +1219,8 @@ static int gip_handle_pkt_audio_format_chat(struct gip_client *client, return -EINVAL; /* chat headsets apparently default to 24 kHz */ - if (pkt->in_out != GIP_AUD_FORMAT_CHAT_24KHZ || in->valid || out->valid) + if (pkt->in_out != GIP_AUD_FORMAT_CHAT_24KHZ || + in->buffer_size || out->buffer_size) return -EPROTO; err = gip_make_audio_config(client, in); @@ -1056,24 +1231,35 @@ static int gip_handle_pkt_audio_format_chat(struct gip_client *client, if (err) return err; - if (!client->drv || !client->drv->ops.audio_ready) - return 0; + if (down_trylock(&client->drv_lock)) + return -EBUSY; - return client->drv->ops.audio_ready(client); + if (client->drv && client->drv->ops.audio_ready) + err = client->drv->ops.audio_ready(client); + + up(&client->drv_lock); + + return err; } static int gip_handle_pkt_audio_volume_chat(struct gip_client *client, void *data, u32 len) { struct gip_pkt_audio_volume_chat *pkt = data; + int err = 0; if (len != sizeof(*pkt)) return -EINVAL; - if (!client->drv || !client->drv->ops.audio_volume) - return 0; + if (down_trylock(&client->drv_lock)) + return -EBUSY; - return client->drv->ops.audio_volume(client, pkt->in, pkt->out); + if (client->drv && client->drv->ops.audio_volume) + err = client->drv->ops.audio_volume(client, pkt->in, pkt->out); + + up(&client->drv_lock); + + return err; } static int gip_handle_pkt_audio_format(struct gip_client *client, @@ -1088,14 +1274,15 @@ static int gip_handle_pkt_audio_format(struct gip_client *client, return -EINVAL; /* format has already been accepted */ - if (in->valid || out->valid) + if (in->buffer_size || out->buffer_size) return -EPROTO; /* client rejected format, accept new format */ if (pkt->in != in->format || pkt->out != out->format) { - dev_warn(&client->dev, "%s: rejected: 0x%02x/0x%02x\n", + gip_warn(client, "%s: rejected: 0x%02x/0x%02x\n", __func__, in->format, out->format); - return gip_suggest_audio_format(client, pkt->in, pkt->out); + return gip_suggest_audio_format(client, pkt->in, pkt->out, + false); } err = gip_make_audio_config(client, in); @@ -1106,24 +1293,35 @@ static int gip_handle_pkt_audio_format(struct gip_client *client, if (err) return err; - if (!client->drv || !client->drv->ops.audio_ready) - return 0; + if (down_trylock(&client->drv_lock)) + return -EBUSY; - return client->drv->ops.audio_ready(client); + if (client->drv && client->drv->ops.audio_ready) + err = client->drv->ops.audio_ready(client); + + up(&client->drv_lock); + + return err; } static int gip_handle_pkt_audio_volume(struct gip_client *client, void *data, u32 len) { struct gip_pkt_audio_volume *pkt = data; + int err = 0; if (len != sizeof(*pkt)) return -EINVAL; - if (!client->drv || !client->drv->ops.audio_volume) - return 0; + if (down_trylock(&client->drv_lock)) + return -EBUSY; - return client->drv->ops.audio_volume(client, pkt->in, pkt->out); + if (client->drv && client->drv->ops.audio_volume) + err = client->drv->ops.audio_volume(client, pkt->in, pkt->out); + + up(&client->drv_lock); + + return err; } static int gip_handle_pkt_audio_control(struct gip_client *client, @@ -1145,7 +1343,7 @@ static int gip_handle_pkt_audio_control(struct gip_client *client, return gip_handle_pkt_audio_volume(client, data, len); } - dev_err(&client->dev, "%s: unknown subcommand: 0x%02x\n", + gip_err(client, "%s: unknown subcommand: 0x%02x\n", __func__, pkt->subcommand); return -EPROTO; @@ -1154,34 +1352,54 @@ static int gip_handle_pkt_audio_control(struct gip_client *client, static int gip_handle_pkt_hid_report(struct gip_client *client, void *data, u32 len) { - if (!client->drv || !client->drv->ops.hid_report) - return 0; + int err = 0; - return client->drv->ops.hid_report(client, data, len); + if (down_trylock(&client->drv_lock)) + return -EBUSY; + + if (client->drv && client->drv->ops.hid_report) + err = client->drv->ops.hid_report(client, data, len); + + up(&client->drv_lock); + + return err; } static int gip_handle_pkt_input(struct gip_client *client, void *data, u32 len) { - if (!client->drv || !client->drv->ops.input) - return 0; + int err = 0; - return client->drv->ops.input(client, data, len); + if (down_trylock(&client->drv_lock)) + return -EBUSY; + + if (client->drv && client->drv->ops.input) + err = client->drv->ops.input(client, data, len); + + up(&client->drv_lock); + + return err; } static int gip_handle_pkt_audio_samples(struct gip_client *client, void *data, u32 len) { struct gip_pkt_audio_samples *pkt = data; + int err = 0; if (len < sizeof(*pkt)) return -EINVAL; - if (!client->drv || !client->drv->ops.audio_samples) - return 0; + if (down_trylock(&client->drv_lock)) + return -EBUSY; - return client->drv->ops.audio_samples(client, pkt->samples, - len - sizeof(*pkt)); + if (client->drv && client->drv->ops.audio_samples) + err = client->drv->ops.audio_samples(client, pkt->samples, + len - sizeof(*pkt)); + + up(&client->drv_lock); + + return err; } static int gip_dispatch_pkt(struct gip_client *client, @@ -1189,12 +1407,16 @@ static int gip_dispatch_pkt(struct gip_client *client, { if (hdr->options & GIP_OPT_INTERNAL) { switch (hdr->command) { + case GIP_CMD_ACKNOWLEDGE: + return gip_handle_pkt_acknowledge(client, data, len); case GIP_CMD_ANNOUNCE: return gip_handle_pkt_announce(client, data, len); case GIP_CMD_STATUS: return gip_handle_pkt_status(client, data, len); case GIP_CMD_IDENTIFY: return gip_handle_pkt_identify(client, data, len); + case GIP_CMD_AUTHENTICATE: + return gip_handle_pkt_authenticate(client, data, len); case GIP_CMD_VIRTUAL_KEY: return gip_handle_pkt_virtual_key(client, data, len); case GIP_CMD_AUDIO_CONTROL: @@ -1216,46 +1438,31 @@ static int gip_dispatch_pkt(struct gip_client *client, return 0; } -static int gip_init_chunk_buffer(struct gip_client *client, u32 len) -{ - struct gip_chunk_buffer *buf = client->chunk_buf; - - if (len > GIP_CHUNK_BUF_MAX_LENGTH) - return -EINVAL; - - if (buf) { - dev_err(&client->dev, "%s: already initialized\n", __func__); - kfree(buf); - client->chunk_buf = NULL; - } - - buf = kzalloc(sizeof(*buf) + len, GFP_ATOMIC); - if (!buf) - return -ENOMEM; - - dev_dbg(&client->dev, "%s: length=0x%04x\n", __func__, len); - buf->length = len; - client->chunk_buf = buf; - - return 0; -} - static int gip_process_pkt_chunked(struct gip_client *client, struct gip_header *hdr, void *data) { - struct gip_chunk_buffer *buf = client->chunk_buf; + struct gip_chunk_buffer *buf = client->chunk_buf_out; int err; - dev_dbg(&client->dev, "%s: offset=0x%04x, length=0x%04x\n", + gip_dbg(client, "%s: offset=0x%04x, length=0x%04x\n", __func__, hdr->chunk_offset, hdr->packet_length); if (!buf) { - dev_err(&client->dev, "%s: buffer not allocated\n", __func__); + /* older gamepads occasionally send spurious completions */ + if (!hdr->packet_length) + return 0; + + gip_err(client, "%s: buffer not allocated\n", __func__); return -EPROTO; } + if (hdr->command != buf->header.command) { + gip_err(client, "%s: conflicting packet\n", __func__); + return -EALREADY; + } + if (buf->length < hdr->chunk_offset + hdr->packet_length) { - dev_err(&client->dev, "%s: buffer too small\n", __func__); + gip_err(client, "%s: buffer too small\n", __func__); return -EINVAL; } @@ -1265,11 +1472,10 @@ static int gip_process_pkt_chunked(struct gip_client *client, } /* empty chunk signals the completion of the transfer */ - /* offset (total transfer length) can be smaller than buffer */ - err = gip_dispatch_pkt(client, hdr, buf->data, hdr->chunk_offset); + err = gip_dispatch_pkt(client, hdr, buf->data, buf->length); kfree(buf); - client->chunk_buf = NULL; + client->chunk_buf_out = NULL; return err; } @@ -1280,8 +1486,8 @@ static int gip_process_pkt(struct gip_client *client, int err; if (hdr->options & GIP_OPT_CHUNK_START) { - /* offset is total length of all chunks */ - err = gip_init_chunk_buffer(client, hdr->chunk_offset); + err = gip_init_chunk_buffer(client, hdr, + &client->chunk_buf_out); if (err) return err; @@ -1300,32 +1506,10 @@ static int gip_process_pkt(struct gip_client *client, return gip_dispatch_pkt(client, hdr, data, hdr->packet_length); } -static int gip_process_adapter_pkt(struct gip_adapter *adap, - struct gip_header *hdr, void *data) -{ - struct gip_client *client; - u8 id = hdr->options & GIP_HDR_CLIENT_ID; - int err = 0; - unsigned long flags; - - client = gip_get_or_init_client(adap, id); - if (IS_ERR(client)) - return PTR_ERR(client); - - spin_lock_irqsave(&client->lock, flags); - - if (atomic_read(&client->state) != GIP_CL_DISCONNECTED) - err = gip_process_pkt(client, hdr, data); - - spin_unlock_irqrestore(&client->lock, flags); - gip_put_client(client); - - return err; -} - int gip_process_buffer(struct gip_adapter *adap, void *data, int len) { struct gip_header hdr; + struct gip_client *client; int hdr_len, err; while (len > GIP_HDR_MIN_LENGTH) { @@ -1333,7 +1517,11 @@ int gip_process_buffer(struct gip_adapter *adap, void *data, int len) if (len < hdr_len + hdr.packet_length) return -EINVAL; - err = gip_process_adapter_pkt(adap, &hdr, data + hdr_len); + client = gip_get_client(adap, hdr.options & GIP_HDR_CLIENT_ID); + if (IS_ERR(client)) + return PTR_ERR(client); + + err = gip_process_pkt(client, &hdr, data + hdr_len); if (err) return err; diff --git a/drivers/hid/xone/bus/protocol.h b/drivers/hid/xone/bus/protocol.h index 203392010..96722c863 100644 --- a/drivers/hid/xone/bus/protocol.h +++ b/drivers/hid/xone/bus/protocol.h @@ -1,27 +1,22 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ /* - * Copyright (C) 2021 Severin von Wnuck + * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #pragma once #include +#include + +#define GIP_VID_MICROSOFT 0x045e /* time between audio packets in ms */ #define GIP_AUDIO_INTERVAL 8 -enum gip_client_state { - GIP_CL_CONNECTED, - GIP_CL_ANNOUNCED, - GIP_CL_IDENTIFIED, - GIP_CL_DISCONNECTED, -}; - enum gip_battery_type { GIP_BATT_TYPE_NONE = 0x00, GIP_BATT_TYPE_STANDARD = 0x01, GIP_BATT_TYPE_KIT = 0x02, - GIP_BATT_TYPE_UNKNOWN = 0x03, }; enum gip_battery_level { @@ -39,6 +34,7 @@ enum gip_power_mode { }; enum gip_audio_format { + GIP_AUD_FORMAT_16KHZ_MONO = 0x05, GIP_AUD_FORMAT_24KHZ_MONO = 0x09, GIP_AUD_FORMAT_48KHZ_STEREO = 0x10, }; @@ -58,7 +54,16 @@ enum gip_led_mode { GIP_LED_FADE_FAST = 0x09, }; +struct gip_header { + u8 command; + u8 options; + u8 sequence; + u32 packet_length; + u32 chunk_offset; +}; + struct gip_chunk_buffer { + struct gip_header header; u32 length; u8 data[]; }; @@ -83,8 +88,6 @@ struct gip_audio_config { int buffer_size; int fragment_size; int packet_size; - - bool valid; }; struct gip_classes { @@ -96,16 +99,20 @@ struct gip_client; struct gip_adapter; int gip_set_power_mode(struct gip_client *client, enum gip_power_mode mode); -int gip_complete_authentication(struct gip_client *client); +int gip_send_authenticate(struct gip_client *client, void *pkt, u32 len, + bool acknowledge); int gip_suggest_audio_format(struct gip_client *client, enum gip_audio_format in, - enum gip_audio_format out); -int gip_fix_audio_volume(struct gip_client *client); + enum gip_audio_format out, + bool chat); +int gip_set_audio_volume(struct gip_client *client, u8 in, u8 chat, u8 out); int gip_send_rumble(struct gip_client *client, void *pkt, u32 len); int gip_set_led_mode(struct gip_client *client, enum gip_led_mode mode, u8 brightness); int gip_send_audio_samples(struct gip_client *client, void *samples); +bool gip_has_interface(struct gip_client *client, const guid_t *guid); +int gip_set_encryption_key(struct gip_client *client, u8 *key, int len); int gip_enable_audio(struct gip_client *client); int gip_init_audio_in(struct gip_client *client); int gip_init_audio_out(struct gip_client *client); diff --git a/drivers/hid/xone/driver/chatpad.c b/drivers/hid/xone/driver/chatpad.c index 5595f16c0..9a7e72f41 100644 --- a/drivers/hid/xone/driver/chatpad.c +++ b/drivers/hid/xone/driver/chatpad.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2021 Severin von Wnuck + * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #include @@ -8,7 +8,7 @@ #include "common.h" -#define GIP_CP_NAME "Microsoft X-Box One chatpad" +#define GIP_CP_NAME "Microsoft Xbox Chatpad" struct gip_chatpad { struct gip_client *client; @@ -134,7 +134,7 @@ static int gip_chatpad_op_guide_button(struct gip_client *client, bool down) } static int gip_chatpad_op_hid_report(struct gip_client *client, - void *data, int len) + void *data, u32 len) { struct gip_chatpad *chatpad = dev_get_drvdata(&client->dev); @@ -183,7 +183,6 @@ static void gip_chatpad_remove(struct gip_client *client) struct gip_chatpad *chatpad = dev_get_drvdata(&client->dev); hid_destroy_device(chatpad->hid_dev); - dev_set_drvdata(&client->dev, NULL); } static struct gip_driver gip_chatpad_driver = { @@ -199,7 +198,7 @@ static struct gip_driver gip_chatpad_driver = { module_gip_driver(gip_chatpad_driver); MODULE_ALIAS("gip:Windows.Xbox.Input.Chatpad"); -MODULE_AUTHOR("Severin von Wnuck "); +MODULE_AUTHOR("Severin von Wnuck-Lipinski "); MODULE_DESCRIPTION("xone GIP chatpad driver"); MODULE_VERSION("#VERSION#"); MODULE_LICENSE("GPL"); diff --git a/drivers/hid/xone/driver/common.c b/drivers/hid/xone/driver/common.c index 142d30c9c..29a8f6dcb 100644 --- a/drivers/hid/xone/driver/common.c +++ b/drivers/hid/xone/driver/common.c @@ -1,9 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2021 Severin von Wnuck + * Copyright (C) 2021 Severin von Wnuck-Lipinski */ -#include #include #include "common.h" @@ -218,8 +217,3 @@ int gip_init_input(struct gip_input *input, struct gip_client *client, return 0; } EXPORT_SYMBOL_GPL(gip_init_input); - -MODULE_AUTHOR("Severin von Wnuck "); -MODULE_DESCRIPTION("xone GIP common driver"); -MODULE_VERSION("#VERSION#"); -MODULE_LICENSE("GPL"); diff --git a/drivers/hid/xone/driver/common.h b/drivers/hid/xone/driver/common.h index 4a4626f12..5db6c5094 100644 --- a/drivers/hid/xone/driver/common.h +++ b/drivers/hid/xone/driver/common.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ /* - * Copyright (C) 2021 Severin von Wnuck + * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #pragma once diff --git a/drivers/hid/xone/driver/gamepad.c b/drivers/hid/xone/driver/gamepad.c index 7c9db3de9..891dbdb9a 100644 --- a/drivers/hid/xone/driver/gamepad.c +++ b/drivers/hid/xone/driver/gamepad.c @@ -1,28 +1,32 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2021 Severin von Wnuck + * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #include +#include #include #include -#include #include "common.h" +#include "../auth/auth.h" -#define GIP_GP_NAME "Microsoft X-Box One pad" - -/* vendor/product ID for the elite controller series 2 */ -#define GIP_GP_VID_MICROSOFT 0x045e -#define GIP_GP_PID_ELITE2 0x0b00 +#define GIP_GP_NAME "Microsoft Xbox Controller" #define GIP_GP_RUMBLE_DELAY msecs_to_jiffies(10) #define GIP_GP_RUMBLE_MAX 100 -static const guid_t gip_gamepad_guid_middle_button = +/* button offset from end of packet */ +#define GIP_GP_BTN_SHARE_OFFSET 18 + +static const guid_t gip_gamepad_guid_share = GUID_INIT(0xecddd2fe, 0xd387, 0x4294, 0xbd, 0x96, 0x1a, 0x71, 0x2e, 0x3d, 0xc7, 0x7d); +static const guid_t gip_gamepad_guid_dli = + GUID_INIT(0x87f2e56b, 0xc3bb, 0x49b1, + 0x82, 0x65, 0xff, 0xff, 0xf3, 0x77, 0x99, 0xee); + enum gip_gamepad_button { GIP_GP_BTN_MENU = BIT(2), GIP_GP_BTN_VIEW = BIT(3), @@ -57,9 +61,9 @@ struct gip_gamepad_pkt_input { __le16 stick_right_y; } __packed; -struct gip_gamepad_pkt_series_xs { - u8 unknown[4]; - u8 share_button; +struct gip_gamepad_pkt_dli { + u32 counter_us1; + u32 counter_us2; } __packed; struct gip_gamepad_pkt_rumble { @@ -77,10 +81,12 @@ struct gip_gamepad_pkt_rumble { struct gip_gamepad { struct gip_client *client; struct gip_battery battery; + struct gip_auth auth; struct gip_led led; struct gip_input input; - bool series_xs; + bool supports_share; + bool supports_dli; struct gip_gamepad_rumble { /* serializes access to rumble packet */ @@ -93,7 +99,12 @@ struct gip_gamepad { static void gip_gamepad_send_rumble(struct timer_list *timer) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 16, 0) struct gip_gamepad_rumble *rumble = from_timer(rumble, timer, timer); +#else + struct gip_gamepad_rumble *rumble = timer_container_of(rumble, timer, + timer); +#endif struct gip_gamepad *gamepad = container_of(rumble, typeof(*gamepad), rumble); unsigned long flags; @@ -119,11 +130,8 @@ static int gip_gamepad_queue_rumble(struct input_dev *dev, void *data, spin_lock_irqsave(&rumble->lock, flags); - rumble->pkt.motors = GIP_GP_MOTOR_R | GIP_GP_MOTOR_L; rumble->pkt.left = (mag_left * GIP_GP_RUMBLE_MAX + S16_MAX) / U16_MAX; rumble->pkt.right = (mag_right * GIP_GP_RUMBLE_MAX + S16_MAX) / U16_MAX; - rumble->pkt.duration = 0xff; - rumble->pkt.repeat = 0xeb; /* delay rumble to work around firmware bug */ if (!timer_pending(&rumble->timer)) @@ -141,7 +149,13 @@ static int gip_gamepad_init_rumble(struct gip_gamepad *gamepad) spin_lock_init(&rumble->lock); timer_setup(&rumble->timer, gip_gamepad_send_rumble, 0); - rumble->last = jiffies; + + /* stop rumble (required for some exotic gamepads to start input) */ + rumble->pkt.motors = GIP_GP_MOTOR_R | GIP_GP_MOTOR_L | + GIP_GP_MOTOR_RT | GIP_GP_MOTOR_LT; + rumble->pkt.duration = 0xff; + rumble->pkt.repeat = 0xeb; + gip_gamepad_send_rumble(&rumble->timer); input_set_capability(dev, EV_FF, FF_RUMBLE); input_set_drvdata(dev, rumble); @@ -149,33 +163,17 @@ static int gip_gamepad_init_rumble(struct gip_gamepad *gamepad) return input_ff_create_memless(dev, NULL, gip_gamepad_queue_rumble); } -static bool gip_gamepad_is_series_xs(struct gip_client *client) -{ - struct gip_hardware *hw = &client->hardware; - guid_t *guid; - int i; - - /* the elite controller also has a middle button */ - if (hw->vendor == GIP_GP_VID_MICROSOFT && - hw->product == GIP_GP_PID_ELITE2) - return false; - - for (i = 0; i < client->interfaces->count; i++) { - guid = (guid_t *)client->interfaces->data + i; - if (guid_equal(guid, &gip_gamepad_guid_middle_button)) - return true; - } - - return false; -} - static int gip_gamepad_init_input(struct gip_gamepad *gamepad) { struct input_dev *dev = gamepad->input.dev; int err; - gamepad->series_xs = gip_gamepad_is_series_xs(gamepad->client); - if (gamepad->series_xs) + gamepad->supports_share = gip_has_interface(gamepad->client, + &gip_gamepad_guid_share); + gamepad->supports_dli = gip_has_interface(gamepad->client, + &gip_gamepad_guid_dli); + + if (gamepad->supports_share) input_set_capability(dev, EV_KEY, KEY_RECORD); input_set_capability(dev, EV_KEY, BTN_MODE); @@ -215,7 +213,11 @@ static int gip_gamepad_init_input(struct gip_gamepad *gamepad) return 0; err_delete_timer: +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0) del_timer_sync(&gamepad->rumble.timer); +#else + timer_delete_sync(&gamepad->rumble.timer); +#endif return err; } @@ -231,6 +233,14 @@ static int gip_gamepad_op_battery(struct gip_client *client, return 0; } +static int gip_gamepad_op_authenticate(struct gip_client *client, + void *data, u32 len) +{ + struct gip_gamepad *gamepad = dev_get_drvdata(&client->dev); + + return gip_auth_process_pkt(&gamepad->auth, data, len); +} + static int gip_gamepad_op_guide_button(struct gip_client *client, bool down) { struct gip_gamepad *gamepad = dev_get_drvdata(&client->dev); @@ -241,22 +251,29 @@ static int gip_gamepad_op_guide_button(struct gip_client *client, bool down) return 0; } -static int gip_gamepad_op_input(struct gip_client *client, void *data, int len) +static int gip_gamepad_op_input(struct gip_client *client, void *data, u32 len) { struct gip_gamepad *gamepad = dev_get_drvdata(&client->dev); struct gip_gamepad_pkt_input *pkt = data; - struct gip_gamepad_pkt_series_xs *pkt_xs = data + sizeof(*pkt); struct input_dev *dev = gamepad->input.dev; - u16 buttons = le16_to_cpu(pkt->buttons); + u16 buttons; + u8 share_offset = GIP_GP_BTN_SHARE_OFFSET; if (len < sizeof(*pkt)) return -EINVAL; - if (gamepad->series_xs) { - if (len < sizeof(*pkt) + sizeof(*pkt_xs)) + buttons = le16_to_cpu(pkt->buttons); + + /* share button byte is always at fixed offset from end of packet */ + if (gamepad->supports_share) { + if (gamepad->supports_dli) + share_offset += sizeof(struct gip_gamepad_pkt_dli); + + if (len < share_offset) return -EINVAL; - input_report_key(dev, KEY_RECORD, !!pkt_xs->share_button); + input_report_key(dev, KEY_RECORD, + ((u8 *)data)[len - share_offset]); } input_report_key(dev, BTN_START, buttons & GIP_GP_BTN_MENU); @@ -307,7 +324,7 @@ static int gip_gamepad_probe(struct gip_client *client) if (err) return err; - err = gip_complete_authentication(client); + err = gip_auth_start_handshake(&gamepad->auth, client); if (err) return err; @@ -328,8 +345,11 @@ static void gip_gamepad_remove(struct gip_client *client) { struct gip_gamepad *gamepad = dev_get_drvdata(&client->dev); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0) del_timer_sync(&gamepad->rumble.timer); - dev_set_drvdata(&client->dev, NULL); +#else + timer_delete_sync(&gamepad->rumble.timer); +#endif } static struct gip_driver gip_gamepad_driver = { @@ -337,6 +357,7 @@ static struct gip_driver gip_gamepad_driver = { .class = "Windows.Xbox.Input.Gamepad", .ops = { .battery = gip_gamepad_op_battery, + .authenticate = gip_gamepad_op_authenticate, .guide_button = gip_gamepad_op_guide_button, .input = gip_gamepad_op_input, }, @@ -346,7 +367,7 @@ static struct gip_driver gip_gamepad_driver = { module_gip_driver(gip_gamepad_driver); MODULE_ALIAS("gip:Windows.Xbox.Input.Gamepad"); -MODULE_AUTHOR("Severin von Wnuck "); +MODULE_AUTHOR("Severin von Wnuck-Lipinski "); MODULE_DESCRIPTION("xone GIP gamepad driver"); MODULE_VERSION("#VERSION#"); MODULE_LICENSE("GPL"); diff --git a/drivers/hid/xone/driver/headset.c b/drivers/hid/xone/driver/headset.c index 434aa54da..311a49cf7 100644 --- a/drivers/hid/xone/driver/headset.c +++ b/drivers/hid/xone/driver/headset.c @@ -1,17 +1,24 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2021 Severin von Wnuck + * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #include +#include #include #include #include #include -#include "../bus/bus.h" +#include "common.h" +#include "../auth/auth.h" -#define GIP_HS_NAME "Microsoft X-Box One headset" +#define GIP_HS_NAME "Microsoft Xbox Headset" + +#define GIP_HS_NUM_BUFFERS 128 + +/* product ID for the chat headset */ +#define GIP_HS_PID_CHAT 0x0111 #define GIP_HS_CONFIG_DELAY msecs_to_jiffies(1000) #define GIP_HS_POWER_ON_DELAY msecs_to_jiffies(1000) @@ -25,15 +32,19 @@ static const struct snd_pcm_hardware gip_headset_pcm_hw = { .formats = SNDRV_PCM_FMTBIT_S16_LE, .rates = SNDRV_PCM_RATE_CONTINUOUS, .periods_min = 2, - .periods_max = 1024, + .periods_max = GIP_HS_NUM_BUFFERS, }; struct gip_headset { struct gip_client *client; + struct gip_battery battery; + struct gip_auth auth; - struct delayed_work config_work; - struct delayed_work power_on_work; - struct work_struct register_work; + bool chat_headset; + + struct delayed_work work_config; + struct delayed_work work_power_on; + struct work_struct work_register; bool registered; struct hrtimer timer; @@ -46,7 +57,6 @@ struct gip_headset { } playback, capture; struct snd_card *card; - struct snd_pcm *pcm; }; static int gip_headset_pcm_open(struct snd_pcm_substream *sub) @@ -64,9 +74,9 @@ static int gip_headset_pcm_open(struct snd_pcm_substream *sub) hw.rate_max = cfg->sample_rate; hw.channels_min = cfg->channels; hw.channels_max = cfg->channels; - hw.buffer_bytes_max = cfg->buffer_size * 8; + hw.buffer_bytes_max = cfg->buffer_size * GIP_HS_NUM_BUFFERS; hw.period_bytes_min = cfg->buffer_size; - hw.period_bytes_max = cfg->buffer_size * 8; + hw.period_bytes_max = cfg->buffer_size; sub->runtime->hw = hw; @@ -78,18 +88,6 @@ static int gip_headset_pcm_close(struct snd_pcm_substream *sub) return 0; } -static int gip_headset_pcm_hw_params(struct snd_pcm_substream *sub, - struct snd_pcm_hw_params *params) -{ - return snd_pcm_lib_alloc_vmalloc_buffer(sub, - params_buffer_bytes(params)); -} - -static int gip_headset_pcm_hw_free(struct snd_pcm_substream *sub) -{ - return snd_pcm_lib_free_vmalloc_buffer(sub); -} - static int gip_headset_pcm_prepare(struct snd_pcm_substream *sub) { return 0; @@ -142,76 +140,69 @@ static snd_pcm_uframes_t gip_headset_pcm_pointer(struct snd_pcm_substream *sub) static const struct snd_pcm_ops gip_headset_pcm_ops = { .open = gip_headset_pcm_open, .close = gip_headset_pcm_close, - .ioctl = snd_pcm_lib_ioctl, - .hw_params = gip_headset_pcm_hw_params, - .hw_free = gip_headset_pcm_hw_free, .prepare = gip_headset_pcm_prepare, .trigger = gip_headset_pcm_trigger, .pointer = gip_headset_pcm_pointer, - .page = snd_pcm_lib_get_vmalloc_page, }; -static bool gip_headset_copy_playback(struct gip_headset_stream *stream, - unsigned char *data, int len) +static bool gip_headset_advance_pointer(struct gip_headset_stream *stream, + int len, size_t buf_size) { - struct snd_pcm_runtime *runtime = stream->substream->runtime; - size_t buf_size = snd_pcm_lib_buffer_bytes(stream->substream); - size_t remaining = buf_size - stream->pointer; - - if (len <= remaining) { - memcpy(data, runtime->dma_area + stream->pointer, len); - } else { - memcpy(data, runtime->dma_area + stream->pointer, remaining); - memcpy(data + remaining, runtime->dma_area, len - remaining); - } + snd_pcm_uframes_t period = stream->substream->runtime->period_size; stream->pointer += len; if (stream->pointer >= buf_size) stream->pointer -= buf_size; stream->period += len; - if (stream->period >= runtime->period_size) { - stream->period -= runtime->period_size; + if (stream->period >= period) { + stream->period -= period; return true; } return false; } -static bool gip_headset_copy_capture(struct gip_headset_stream *stream, - unsigned char *data, int len) +static bool gip_headset_copy_playback(struct gip_headset_stream *stream, + unsigned char *data, int len) { - struct snd_pcm_runtime *runtime = stream->substream->runtime; + unsigned char *src = stream->substream->runtime->dma_area; size_t buf_size = snd_pcm_lib_buffer_bytes(stream->substream); size_t remaining = buf_size - stream->pointer; if (len <= remaining) { - memcpy(runtime->dma_area + stream->pointer, data, len); + memcpy(data, src + stream->pointer, len); } else { - memcpy(runtime->dma_area + stream->pointer, data, remaining); - memcpy(runtime->dma_area, data + remaining, len - remaining); + memcpy(data, src + stream->pointer, remaining); + memcpy(data + remaining, src, len - remaining); } - stream->pointer += len; - if (stream->pointer >= buf_size) - stream->pointer -= buf_size; + return gip_headset_advance_pointer(stream, len, buf_size); +} - stream->period += len; - if (stream->period >= runtime->period_size) { - stream->period -= runtime->period_size; - return true; +static bool gip_headset_copy_capture(struct gip_headset_stream *stream, + unsigned char *data, int len) +{ + unsigned char *dest = stream->substream->runtime->dma_area; + size_t buf_size = snd_pcm_lib_buffer_bytes(stream->substream); + size_t remaining = buf_size - stream->pointer; + + if (len <= remaining) { + memcpy(dest + stream->pointer, data, len); + } else { + memcpy(dest + stream->pointer, data, remaining); + memcpy(dest, data + remaining, len - remaining); } - return false; + return gip_headset_advance_pointer(stream, len, buf_size); } static enum hrtimer_restart gip_headset_send_samples(struct hrtimer *timer) { struct gip_headset *headset = container_of(timer, typeof(*headset), timer); - struct gip_headset_stream *stream = &headset->playback; struct gip_audio_config *cfg = &headset->client->audio_config_out; - struct snd_pcm_substream *sub = stream->substream; + struct snd_pcm_substream *sub = headset->playback.substream; bool elapsed = false; int err; unsigned long flags; @@ -220,7 +211,7 @@ static enum hrtimer_restart gip_headset_send_samples(struct hrtimer *timer) snd_pcm_stream_lock_irqsave(sub, flags); if (sub->runtime && snd_pcm_running(sub)) - elapsed = gip_headset_copy_playback(stream, + elapsed = gip_headset_copy_playback(&headset->playback, headset->buffer, cfg->buffer_size); @@ -240,9 +231,10 @@ static enum hrtimer_restart gip_headset_send_samples(struct hrtimer *timer) return HRTIMER_RESTART; } -static int gip_headset_init_card(struct gip_headset *headset) +static int gip_headset_init_pcm(struct gip_headset *headset) { struct snd_card *card; + struct snd_pcm *pcm; int err; err = snd_card_new(&headset->client->dev, SNDRV_DEFAULT_IDX1, @@ -250,67 +242,32 @@ static int gip_headset_init_card(struct gip_headset *headset) if (err) return err; - strscpy(card->driver, "GIP Headset", sizeof(card->driver)); + strscpy(card->driver, "xone-gip-headset", sizeof(card->driver)); strscpy(card->shortname, GIP_HS_NAME, sizeof(card->shortname)); snprintf(card->longname, sizeof(card->longname), "%s at %s", GIP_HS_NAME, dev_name(&headset->client->dev)); headset->card = card; - return 0; -} - -static int gip_headset_init_pcm(struct gip_headset *headset) -{ - struct gip_client *client = headset->client; - struct snd_pcm *pcm; - int err; - - err = snd_pcm_new(headset->card, "GIP Headset", 0, 1, 1, &pcm); + err = snd_pcm_new(card, GIP_HS_NAME, 0, 1, 1, &pcm); if (err) return err; - strscpy(pcm->name, "GIP Headset", sizeof(pcm->name)); + strscpy(pcm->name, GIP_HS_NAME, sizeof(pcm->name)); pcm->private_data = headset; snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &gip_headset_pcm_ops); snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &gip_headset_pcm_ops); + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0); - headset->buffer = devm_kzalloc(&client->dev, - client->audio_config_out.buffer_size, - GFP_KERNEL); - if (!headset->buffer) - return -ENOMEM; - - headset->pcm = pcm; - - return snd_card_register(headset->card); -} - -static int gip_headset_start_audio(struct gip_headset *headset) -{ - struct gip_client *client = headset->client; - int err; - - /* set headset volume to maximum */ - err = gip_fix_audio_volume(client); - if (err) - return err; - - err = gip_init_audio_out(client); - if (err) - return err; - - hrtimer_start(&headset->timer, 0, HRTIMER_MODE_REL); - - return 0; + return snd_card_register(card); } static void gip_headset_config(struct work_struct *work) { struct gip_headset *headset = container_of(to_delayed_work(work), typeof(*headset), - config_work); + work_config); struct gip_client *client = headset->client; struct gip_info_element *fmts = client->audio_formats; int err; @@ -319,7 +276,8 @@ static void gip_headset_config(struct work_struct *work) fmts->data[0], fmts->data[1]); /* suggest initial audio format */ - err = gip_suggest_audio_format(client, fmts->data[0], fmts->data[1]); + err = gip_suggest_audio_format(client, fmts->data[0], fmts->data[1], + headset->chat_headset); if (err) dev_err(&client->dev, "%s: suggest format failed: %d\n", __func__, err); @@ -329,65 +287,111 @@ static void gip_headset_power_on(struct work_struct *work) { struct gip_headset *headset = container_of(to_delayed_work(work), typeof(*headset), - power_on_work); + work_power_on); struct gip_client *client = headset->client; int err; err = gip_set_power_mode(client, GIP_PWR_ON); - if (err) + if (err) { dev_err(&client->dev, "%s: set power mode failed: %d\n", __func__, err); + return; + } + + /* not a standalone headset */ + if (client->id) + return; + + err = gip_init_battery(&headset->battery, client, GIP_HS_NAME); + if (err) { + dev_err(&client->dev, "%s: init battery failed: %d\n", + __func__, err); + return; + } + + err = gip_auth_start_handshake(&headset->auth, client); + if (err) + dev_err(&client->dev, "%s: start handshake failed: %d\n", + __func__, err); } static void gip_headset_register(struct work_struct *work) { struct gip_headset *headset = container_of(work, typeof(*headset), - register_work); - struct device *dev = &headset->client->dev; + work_register); + struct gip_client *client = headset->client; int err; - err = gip_headset_init_card(headset); - if (err) { - dev_err(dev, "%s: init card failed: %d\n", __func__, err); + headset->buffer = devm_kzalloc(&client->dev, + client->audio_config_out.buffer_size, + GFP_KERNEL); + if (!headset->buffer) return; - } err = gip_headset_init_pcm(headset); if (err) { - dev_err(dev, "%s: init PCM failed: %d\n", __func__, err); - goto err_free_card; + dev_err(&client->dev, "%s: init PCM failed: %d\n", + __func__, err); + return; } - err = gip_headset_start_audio(headset); + /* set hardware volume to maximum for headset jack */ + /* standalone & chat headsets have physical volume controls */ + if (client->id && !headset->chat_headset) { + err = gip_set_audio_volume(client, 100, 50, 100); + if (err) { + dev_err(&client->dev, "%s: set volume failed: %d\n", + __func__, err); + return; + } + } + + err = gip_init_audio_out(client); if (err) { - dev_err(dev, "%s: start audio failed: %d\n", __func__, err); - goto err_free_card; + dev_err(&client->dev, "%s: init audio out failed: %d\n", + __func__, err); + return; } - return; + hrtimer_start(&headset->timer, 0, HRTIMER_MODE_REL); +} -err_free_card: - snd_card_free(headset->card); - headset->card = NULL; +static int gip_headset_op_battery(struct gip_client *client, + enum gip_battery_type type, + enum gip_battery_level level) +{ + struct gip_headset *headset = dev_get_drvdata(&client->dev); + + gip_report_battery(&headset->battery, type, level); + + return 0; +} + +static int gip_headset_op_authenticate(struct gip_client *client, + void *data, u32 len) +{ + struct gip_headset *headset = dev_get_drvdata(&client->dev); + + return gip_auth_process_pkt(&headset->auth, data, len); } static int gip_headset_op_audio_ready(struct gip_client *client) { struct gip_headset *headset = dev_get_drvdata(&client->dev); - schedule_delayed_work(&headset->power_on_work, GIP_HS_POWER_ON_DELAY); + schedule_delayed_work(&headset->work_power_on, GIP_HS_POWER_ON_DELAY); return 0; } static int gip_headset_op_audio_volume(struct gip_client *client, - int in, int out) + u8 in, u8 out) { struct gip_headset *headset = dev_get_drvdata(&client->dev); /* headset reported initial volume, start audio I/O */ if (!headset->registered) { - schedule_work(&headset->register_work); + schedule_work(&headset->work_register); headset->registered = true; } @@ -396,11 +400,10 @@ static int gip_headset_op_audio_volume(struct gip_client *client, } static int gip_headset_op_audio_samples(struct gip_client *client, - void *data, int len) + void *data, u32 len) { struct gip_headset *headset = dev_get_drvdata(&client->dev); - struct gip_headset_stream *stream = &headset->capture; - struct snd_pcm_substream *sub = stream->substream; + struct snd_pcm_substream *sub = headset->capture.substream; bool elapsed = false; unsigned long flags; @@ -410,7 +413,8 @@ static int gip_headset_op_audio_samples(struct gip_client *client, snd_pcm_stream_lock_irqsave(sub, flags); if (sub->runtime && snd_pcm_running(sub)) - elapsed = gip_headset_copy_capture(stream, data, len); + elapsed = gip_headset_copy_capture(&headset->capture, + data, len); snd_pcm_stream_unlock_irqrestore(sub, flags); @@ -434,13 +438,20 @@ static int gip_headset_probe(struct gip_client *client) return -ENOMEM; headset->client = client; + headset->chat_headset = client->hardware.vendor == GIP_VID_MICROSOFT && + client->hardware.product == GIP_HS_PID_CHAT; - INIT_DELAYED_WORK(&headset->config_work, gip_headset_config); - INIT_DELAYED_WORK(&headset->power_on_work, gip_headset_power_on); - INIT_WORK(&headset->register_work, gip_headset_register); + INIT_DELAYED_WORK(&headset->work_config, gip_headset_config); + INIT_DELAYED_WORK(&headset->work_power_on, gip_headset_power_on); + INIT_WORK(&headset->work_register, gip_headset_register); +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 13, 0) hrtimer_init(&headset->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); headset->timer.function = gip_headset_send_samples; +#else + hrtimer_setup(&headset->timer, gip_headset_send_samples, + CLOCK_MONOTONIC, HRTIMER_MODE_REL); +#endif err = gip_enable_audio(client); if (err) @@ -455,7 +466,7 @@ static int gip_headset_probe(struct gip_client *client) dev_set_drvdata(&client->dev, headset); /* delay to prevent response from being dropped */ - schedule_delayed_work(&headset->config_work, GIP_HS_CONFIG_DELAY); + schedule_delayed_work(&headset->work_config, GIP_HS_CONFIG_DELAY); return 0; } @@ -464,9 +475,9 @@ static void gip_headset_remove(struct gip_client *client) { struct gip_headset *headset = dev_get_drvdata(&client->dev); - cancel_delayed_work_sync(&headset->config_work); - cancel_delayed_work_sync(&headset->power_on_work); - cancel_work_sync(&headset->register_work); + cancel_delayed_work_sync(&headset->work_config); + cancel_delayed_work_sync(&headset->work_power_on); + cancel_work_sync(&headset->work_register); hrtimer_cancel(&headset->timer); gip_disable_audio(client); @@ -474,14 +485,14 @@ static void gip_headset_remove(struct gip_client *client) snd_card_disconnect(headset->card); snd_card_free_when_closed(headset->card); } - - dev_set_drvdata(&client->dev, NULL); } static struct gip_driver gip_headset_driver = { .name = "xone-gip-headset", .class = "Windows.Xbox.Input.Headset", .ops = { + .battery = gip_headset_op_battery, + .authenticate = gip_headset_op_authenticate, .audio_ready = gip_headset_op_audio_ready, .audio_volume = gip_headset_op_audio_volume, .audio_samples = gip_headset_op_audio_samples, @@ -492,7 +503,7 @@ static struct gip_driver gip_headset_driver = { module_gip_driver(gip_headset_driver); MODULE_ALIAS("gip:Windows.Xbox.Input.Headset"); -MODULE_AUTHOR("Severin von Wnuck "); +MODULE_AUTHOR("Severin von Wnuck-Lipinski "); MODULE_DESCRIPTION("xone GIP headset driver"); MODULE_VERSION("#VERSION#"); MODULE_LICENSE("GPL"); diff --git a/drivers/hid/xone/driver/madcatz_glam.c b/drivers/hid/xone/driver/madcatz_glam.c new file mode 100644 index 000000000..50d067ce5 --- /dev/null +++ b/drivers/hid/xone/driver/madcatz_glam.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2024 Severin von Wnuck-Lipinski + */ + +#include + +#include "common.h" +#include "../auth/auth.h" + +#define GIP_GL_NAME "Mad Catz Rock Band 4 Drum Kit" + +enum gip_glam_button { + GIP_GL_BTN_MENU = BIT(2), + GIP_GL_BTN_VIEW = BIT(3), + GIP_GL_BTN_A = BIT(4), + GIP_GL_BTN_B = BIT(5), + /* swapped X and Y buttons */ + GIP_GL_BTN_X = BIT(7), + GIP_GL_BTN_Y = BIT(6), + GIP_GL_BTN_DPAD_U = BIT(8), + GIP_GL_BTN_DPAD_D = BIT(9), + GIP_GL_BTN_DPAD_L = BIT(10), + GIP_GL_BTN_DPAD_R = BIT(11), + GIP_GL_BTN_KICK_1 = BIT(12), + GIP_GL_BTN_KICK_2 = BIT(13), +}; + +enum gip_glam_pad { + GIP_GL_PAD_YELLOW = BIT(0) | BIT(1) | BIT(2), + GIP_GL_PAD_RED = BIT(4) | BIT(5) | BIT(6), + GIP_GL_PAD_GREEN = BIT(8) | BIT(9) | BIT(10), + GIP_GL_PAD_BLUE = BIT(12) | BIT(13) | BIT(14), +}; + +enum gip_glam_cymbal { + GIP_GL_CBL_BLUE = BIT(0) | BIT(1) | BIT(2), + GIP_GL_CBL_YELLOW = BIT(4) | BIT(5) | BIT(6), + GIP_GL_CBL_GREEN = BIT(12) | BIT(13) | BIT(14), +}; + +struct gip_glam_pkt_input { + __le16 buttons; + __le16 pads; + __le16 cymbals; +} __packed; + +struct gip_glam { + struct gip_client *client; + struct gip_battery battery; + struct gip_input input; +}; + +static int gip_glam_init_input(struct gip_glam *glam) +{ + struct input_dev *dev = glam->input.dev; + int err; + + input_set_capability(dev, EV_KEY, BTN_MODE); + input_set_capability(dev, EV_KEY, BTN_START); + input_set_capability(dev, EV_KEY, BTN_SELECT); + input_set_capability(dev, EV_KEY, BTN_A); + input_set_capability(dev, EV_KEY, BTN_B); + input_set_capability(dev, EV_KEY, BTN_X); + input_set_capability(dev, EV_KEY, BTN_Y); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY1); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY2); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY3); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY4); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY5); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY6); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY7); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY8); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY9); + input_set_abs_params(dev, ABS_HAT0X, -1, 1, 0, 0); + input_set_abs_params(dev, ABS_HAT0Y, -1, 1, 0, 0); + + err = input_register_device(dev); + if (err) + dev_err(&glam->client->dev, "%s: register failed: %d\n", + __func__, err); + + return err; +} + +static int gip_glam_op_battery(struct gip_client *client, + enum gip_battery_type type, + enum gip_battery_level level) +{ + struct gip_glam *glam = dev_get_drvdata(&client->dev); + + gip_report_battery(&glam->battery, type, level); + + return 0; +} + +static int gip_glam_op_guide_button(struct gip_client *client, bool down) +{ + struct gip_glam *glam = dev_get_drvdata(&client->dev); + + input_report_key(glam->input.dev, BTN_MODE, down); + input_sync(glam->input.dev); + + return 0; +} + +static int gip_glam_op_input(struct gip_client *client, void *data, u32 len) +{ + struct gip_glam *glam = dev_get_drvdata(&client->dev); + struct gip_glam_pkt_input *pkt = data; + struct input_dev *dev = glam->input.dev; + u16 buttons; + u16 pads; + u16 cymbals; + + if (len < sizeof(*pkt)) + return -EINVAL; + + buttons = le16_to_cpu(pkt->buttons); + pads = le16_to_cpu(pkt->pads); + cymbals = le16_to_cpu(pkt->cymbals); + + input_report_key(dev, BTN_START, buttons & GIP_GL_BTN_MENU); + input_report_key(dev, BTN_SELECT, buttons & GIP_GL_BTN_VIEW); + input_report_key(dev, BTN_A, buttons & GIP_GL_BTN_A); + input_report_key(dev, BTN_B, buttons & GIP_GL_BTN_B); + input_report_key(dev, BTN_X, buttons & GIP_GL_BTN_X); + input_report_key(dev, BTN_Y, buttons & GIP_GL_BTN_Y); + input_report_key(dev, BTN_TRIGGER_HAPPY1, buttons & GIP_GL_BTN_KICK_1); + input_report_key(dev, BTN_TRIGGER_HAPPY2, buttons & GIP_GL_BTN_KICK_2); + input_report_key(dev, BTN_TRIGGER_HAPPY3, pads & GIP_GL_PAD_RED); + input_report_key(dev, BTN_TRIGGER_HAPPY4, pads & GIP_GL_PAD_YELLOW); + input_report_key(dev, BTN_TRIGGER_HAPPY5, pads & GIP_GL_PAD_BLUE); + input_report_key(dev, BTN_TRIGGER_HAPPY6, pads & GIP_GL_PAD_GREEN); + input_report_key(dev, BTN_TRIGGER_HAPPY7, cymbals & GIP_GL_CBL_YELLOW); + input_report_key(dev, BTN_TRIGGER_HAPPY8, cymbals & GIP_GL_CBL_BLUE); + input_report_key(dev, BTN_TRIGGER_HAPPY9, cymbals & GIP_GL_CBL_GREEN); + input_report_abs(dev, ABS_HAT0X, !!(buttons & GIP_GL_BTN_DPAD_R) - + !!(buttons & GIP_GL_BTN_DPAD_L)); + input_report_abs(dev, ABS_HAT0Y, !!(buttons & GIP_GL_BTN_DPAD_D) - + !!(buttons & GIP_GL_BTN_DPAD_U)); + input_sync(dev); + + return 0; +} + +static int gip_glam_probe(struct gip_client *client) +{ + struct gip_glam *glam; + int err; + + glam = devm_kzalloc(&client->dev, sizeof(*glam), GFP_KERNEL); + if (!glam) + return -ENOMEM; + + glam->client = client; + + err = gip_set_power_mode(client, GIP_PWR_ON); + if (err) + return err; + + err = gip_init_battery(&glam->battery, client, GIP_GL_NAME); + if (err) + return err; + + /* + * The Drum Kit sends auth chunks without specifying the + * acknowledgment option while still expecting an acknowledgment. + * The Windows driver handles this by sending an acknowledgment + * after 100 ms when no further chunks are received. + * We skip the handshake instead, as it is not required. + */ + err = gip_auth_send_complete(client); + if (err) + return err; + + err = gip_init_input(&glam->input, client, GIP_GL_NAME); + if (err) + return err; + + err = gip_glam_init_input(glam); + if (err) + return err; + + dev_set_drvdata(&client->dev, glam); + + return 0; +} + +static struct gip_driver gip_glam_driver = { + .name = "xone-gip-madcatz-glam", + .class = "MadCatz.Xbox.Drums.Glam", + .ops = { + .battery = gip_glam_op_battery, + .guide_button = gip_glam_op_guide_button, + .input = gip_glam_op_input, + }, + .probe = gip_glam_probe, +}; +module_gip_driver(gip_glam_driver); + +MODULE_ALIAS("gip:MadCatz.Xbox.Drums.Glam"); +MODULE_AUTHOR("Severin von Wnuck-Lipinski "); +MODULE_DESCRIPTION("xone GIP Mad Catz Drum Kit driver"); +MODULE_VERSION("#VERSION#"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/xone/driver/madcatz_strat.c b/drivers/hid/xone/driver/madcatz_strat.c new file mode 100644 index 000000000..445dd1944 --- /dev/null +++ b/drivers/hid/xone/driver/madcatz_strat.c @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2022 Severin von Wnuck-Lipinski + */ + +#include + +#include "common.h" +#include "../auth/auth.h" + +#define GIP_ST_NAME "Mad Catz Rock Band 4 Stratocaster" + +enum gip_strat_button { + GIP_ST_BTN_MENU = BIT(2), + GIP_ST_BTN_VIEW = BIT(3), + GIP_ST_BTN_DPAD_U = BIT(8), + GIP_ST_BTN_DPAD_D = BIT(9), + GIP_ST_BTN_DPAD_L = BIT(10), + GIP_ST_BTN_DPAD_R = BIT(11), +}; + +enum gip_strat_fret { + GIP_ST_FRET_GREEN = BIT(0), + GIP_ST_FRET_RED = BIT(1), + GIP_ST_FRET_YELLOW = BIT(2), + GIP_ST_FRET_BLUE = BIT(3), + GIP_ST_FRET_ORANGE = BIT(4), +}; + +struct gip_strat_pkt_input { + __le16 buttons; + u8 tilt; + u8 whammy; + u8 slider; + u8 fret_upper; + u8 fret_lower; +} __packed; + +struct gip_strat { + struct gip_client *client; + struct gip_battery battery; + struct gip_input input; +}; + +static int gip_strat_init_input(struct gip_strat *strat) +{ + struct input_dev *dev = strat->input.dev; + int err; + + input_set_capability(dev, EV_KEY, BTN_MODE); + input_set_capability(dev, EV_KEY, BTN_START); + input_set_capability(dev, EV_KEY, BTN_SELECT); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY1); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY2); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY3); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY4); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY5); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY6); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY7); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY8); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY9); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY10); + input_set_abs_params(dev, ABS_X, 0, 64, 0, 0); + input_set_abs_params(dev, ABS_Y, 0, 255, 0, 0); + input_set_abs_params(dev, ABS_Z, 0, 255, 0, 0); + input_set_abs_params(dev, ABS_HAT0X, -1, 1, 0, 0); + input_set_abs_params(dev, ABS_HAT0Y, -1, 1, 0, 0); + + err = input_register_device(dev); + if (err) + dev_err(&strat->client->dev, "%s: register failed: %d\n", + __func__, err); + + return err; +} + +static int gip_strat_op_battery(struct gip_client *client, + enum gip_battery_type type, + enum gip_battery_level level) +{ + struct gip_strat *strat = dev_get_drvdata(&client->dev); + + gip_report_battery(&strat->battery, type, level); + + return 0; +} + +static int gip_strat_op_guide_button(struct gip_client *client, bool down) +{ + struct gip_strat *strat = dev_get_drvdata(&client->dev); + + input_report_key(strat->input.dev, BTN_MODE, down); + input_sync(strat->input.dev); + + return 0; +} + +static int gip_strat_op_input(struct gip_client *client, void *data, u32 len) +{ + struct gip_strat *strat = dev_get_drvdata(&client->dev); + struct gip_strat_pkt_input *pkt = data; + struct input_dev *dev = strat->input.dev; + u16 buttons; + + if (len < sizeof(*pkt)) + return -EINVAL; + + buttons = le16_to_cpu(pkt->buttons); + + input_report_key(dev, BTN_START, buttons & GIP_ST_BTN_MENU); + input_report_key(dev, BTN_SELECT, buttons & GIP_ST_BTN_VIEW); + input_report_key(dev, BTN_TRIGGER_HAPPY1, + pkt->fret_upper & GIP_ST_FRET_GREEN); + input_report_key(dev, BTN_TRIGGER_HAPPY2, + pkt->fret_upper & GIP_ST_FRET_RED); + input_report_key(dev, BTN_TRIGGER_HAPPY3, + pkt->fret_upper & GIP_ST_FRET_YELLOW); + input_report_key(dev, BTN_TRIGGER_HAPPY4, + pkt->fret_upper & GIP_ST_FRET_BLUE); + input_report_key(dev, BTN_TRIGGER_HAPPY5, + pkt->fret_upper & GIP_ST_FRET_ORANGE); + input_report_key(dev, BTN_TRIGGER_HAPPY6, + pkt->fret_lower & GIP_ST_FRET_GREEN); + input_report_key(dev, BTN_TRIGGER_HAPPY7, + pkt->fret_lower & GIP_ST_FRET_RED); + input_report_key(dev, BTN_TRIGGER_HAPPY8, + pkt->fret_lower & GIP_ST_FRET_YELLOW); + input_report_key(dev, BTN_TRIGGER_HAPPY9, + pkt->fret_lower & GIP_ST_FRET_BLUE); + input_report_key(dev, BTN_TRIGGER_HAPPY10, + pkt->fret_lower & GIP_ST_FRET_ORANGE); + input_report_abs(dev, ABS_X, pkt->slider); + input_report_abs(dev, ABS_Y, pkt->whammy); + input_report_abs(dev, ABS_Z, pkt->tilt); + input_report_abs(dev, ABS_HAT0X, !!(buttons & GIP_ST_BTN_DPAD_R) - + !!(buttons & GIP_ST_BTN_DPAD_L)); + input_report_abs(dev, ABS_HAT0Y, !!(buttons & GIP_ST_BTN_DPAD_D) - + !!(buttons & GIP_ST_BTN_DPAD_U)); + input_sync(dev); + + return 0; +} + +static int gip_strat_probe(struct gip_client *client) +{ + struct gip_strat *strat; + int err; + + strat = devm_kzalloc(&client->dev, sizeof(*strat), GFP_KERNEL); + if (!strat) + return -ENOMEM; + + strat->client = client; + + err = gip_set_power_mode(client, GIP_PWR_ON); + if (err) + return err; + + err = gip_init_battery(&strat->battery, client, GIP_ST_NAME); + if (err) + return err; + + /* + * The Stratocaster sends auth chunks without specifying the + * acknowledgment option while still expecting an acknowledgment. + * The Windows driver handles this by sending an acknowledgment + * after 100 ms when no further chunks are received. + * We skip the handshake instead, as it is not required. + */ + err = gip_auth_send_complete(client); + if (err) + return err; + + err = gip_init_input(&strat->input, client, GIP_ST_NAME); + if (err) + return err; + + err = gip_strat_init_input(strat); + if (err) + return err; + + dev_set_drvdata(&client->dev, strat); + + return 0; +} + +static struct gip_driver gip_strat_driver = { + .name = "xone-gip-madcatz-strat", + .class = "MadCatz.Xbox.Guitar.Stratocaster", + .ops = { + .battery = gip_strat_op_battery, + .guide_button = gip_strat_op_guide_button, + .input = gip_strat_op_input, + }, + .probe = gip_strat_probe, +}; +module_gip_driver(gip_strat_driver); + +MODULE_ALIAS("gip:MadCatz.Xbox.Guitar.Stratocaster"); +MODULE_AUTHOR("Severin von Wnuck-Lipinski "); +MODULE_DESCRIPTION("xone GIP Mad Catz Stratocaster driver"); +MODULE_VERSION("#VERSION#"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/xone/driver/pdp_jaguar.c b/drivers/hid/xone/driver/pdp_jaguar.c new file mode 100644 index 000000000..b98caf5fa --- /dev/null +++ b/drivers/hid/xone/driver/pdp_jaguar.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2022 Severin von Wnuck-Lipinski + * Copyright (C) 2023 Scott K Logan + */ + +#include + +#include "common.h" +#include "../auth/auth.h" + +#define GIP_JA_NAME "PDP Rock Band 4 Jaguar" + +enum gip_jaguar_button { + GIP_JA_BTN_MENU = BIT(2), + GIP_JA_BTN_VIEW = BIT(3), + GIP_JA_BTN_DPAD_U = BIT(8), + GIP_JA_BTN_DPAD_D = BIT(9), + GIP_JA_BTN_DPAD_L = BIT(10), + GIP_JA_BTN_DPAD_R = BIT(11), +}; + +enum gip_jaguar_fret { + GIP_JA_FRET_GREEN = BIT(4), + GIP_JA_FRET_RED = BIT(5), + GIP_JA_FRET_BLUE = BIT(6), + GIP_JA_FRET_YELLOW = BIT(7), + GIP_JA_FRET_ORANGE = BIT(12), + GIP_JA_FRET_LOWER = BIT(14), +}; + +struct gip_jaguar_pkt_input { + __le16 buttons; + u8 tilt; + u8 whammy; +} __packed; + +struct gip_jaguar { + struct gip_client *client; + struct gip_battery battery; + struct gip_auth auth; + struct gip_input input; +}; + +static int gip_jaguar_init_input(struct gip_jaguar *guitar) +{ + struct input_dev *dev = guitar->input.dev; + int err; + + input_set_capability(dev, EV_KEY, BTN_MODE); + input_set_capability(dev, EV_KEY, BTN_START); + input_set_capability(dev, EV_KEY, BTN_SELECT); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY1); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY2); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY3); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY4); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY5); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY6); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY7); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY8); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY9); + input_set_capability(dev, EV_KEY, BTN_TRIGGER_HAPPY10); + input_set_abs_params(dev, ABS_Y, 0, 255, 0, 0); + input_set_abs_params(dev, ABS_Z, 0, 255, 0, 0); + input_set_abs_params(dev, ABS_HAT0X, -1, 1, 0, 0); + input_set_abs_params(dev, ABS_HAT0Y, -1, 1, 0, 0); + + err = input_register_device(dev); + if (err) + dev_err(&guitar->client->dev, "%s: register failed: %d\n", + __func__, err); + + return err; +} + +static int gip_jaguar_op_battery(struct gip_client *client, + enum gip_battery_type type, + enum gip_battery_level level) +{ + struct gip_jaguar *guitar = dev_get_drvdata(&client->dev); + + gip_report_battery(&guitar->battery, type, level); + + return 0; +} + +static int gip_jaguar_op_authenticate(struct gip_client *client, + void *data, u32 len) +{ + struct gip_jaguar *guitar = dev_get_drvdata(&client->dev); + + return gip_auth_process_pkt(&guitar->auth, data, len); +} + +static int gip_jaguar_op_guide_button(struct gip_client *client, bool down) +{ + struct gip_jaguar *guitar = dev_get_drvdata(&client->dev); + + input_report_key(guitar->input.dev, BTN_MODE, down); + input_sync(guitar->input.dev); + + return 0; +} + +static int gip_jaguar_op_input(struct gip_client *client, void *data, u32 len) +{ + struct gip_jaguar *guitar = dev_get_drvdata(&client->dev); + struct gip_jaguar_pkt_input *pkt = data; + struct input_dev *dev = guitar->input.dev; + u16 buttons; + bool lower; + + if (len < sizeof(*pkt)) + return -EINVAL; + + buttons = le16_to_cpu(pkt->buttons); + lower = buttons & GIP_JA_FRET_LOWER; + + input_report_key(dev, BTN_START, buttons & GIP_JA_BTN_MENU); + input_report_key(dev, BTN_SELECT, buttons & GIP_JA_BTN_VIEW); + input_report_key(dev, BTN_TRIGGER_HAPPY1, + (buttons & GIP_JA_FRET_GREEN) && !lower); + input_report_key(dev, BTN_TRIGGER_HAPPY2, + (buttons & GIP_JA_FRET_RED) && !lower); + input_report_key(dev, BTN_TRIGGER_HAPPY3, + (buttons & GIP_JA_FRET_YELLOW) && !lower); + input_report_key(dev, BTN_TRIGGER_HAPPY4, + (buttons & GIP_JA_FRET_BLUE) && !lower); + input_report_key(dev, BTN_TRIGGER_HAPPY5, + (buttons & GIP_JA_FRET_ORANGE) && !lower); + input_report_key(dev, BTN_TRIGGER_HAPPY6, + (buttons & GIP_JA_FRET_GREEN) && lower); + input_report_key(dev, BTN_TRIGGER_HAPPY7, + (buttons & GIP_JA_FRET_RED) && lower); + input_report_key(dev, BTN_TRIGGER_HAPPY8, + (buttons & GIP_JA_FRET_YELLOW) && lower); + input_report_key(dev, BTN_TRIGGER_HAPPY9, + (buttons & GIP_JA_FRET_BLUE) && lower); + input_report_key(dev, BTN_TRIGGER_HAPPY10, + (buttons & GIP_JA_FRET_ORANGE) && lower); + input_report_abs(dev, ABS_Y, pkt->whammy); + input_report_abs(dev, ABS_Z, pkt->tilt); + input_report_abs(dev, ABS_HAT0X, !!(buttons & GIP_JA_BTN_DPAD_R) - + !!(buttons & GIP_JA_BTN_DPAD_L)); + input_report_abs(dev, ABS_HAT0Y, !!(buttons & GIP_JA_BTN_DPAD_D) - + !!(buttons & GIP_JA_BTN_DPAD_U)); + input_sync(dev); + + return 0; +} + +static int gip_jaguar_probe(struct gip_client *client) +{ + struct gip_jaguar *guitar; + int err; + + guitar = devm_kzalloc(&client->dev, sizeof(*guitar), GFP_KERNEL); + if (!guitar) + return -ENOMEM; + + guitar->client = client; + + err = gip_set_power_mode(client, GIP_PWR_ON); + if (err) + return err; + + err = gip_init_battery(&guitar->battery, client, GIP_JA_NAME); + if (err) + return err; + + err = gip_auth_start_handshake(&guitar->auth, client); + if (err) + return err; + + err = gip_init_input(&guitar->input, client, GIP_JA_NAME); + if (err) + return err; + + err = gip_jaguar_init_input(guitar); + if (err) + return err; + + dev_set_drvdata(&client->dev, guitar); + + return 0; +} + +static struct gip_driver gip_jaguar_driver = { + .name = "xone-gip-pdp-jaguar", + .class = "PDP.Xbox.Guitar.Jaguar", + .ops = { + .battery = gip_jaguar_op_battery, + .authenticate = gip_jaguar_op_authenticate, + .guide_button = gip_jaguar_op_guide_button, + .input = gip_jaguar_op_input, + }, + .probe = gip_jaguar_probe, +}; +module_gip_driver(gip_jaguar_driver); + +MODULE_ALIAS("gip:PDP.Xbox.Guitar.Jaguar"); +MODULE_AUTHOR("Severin von Wnuck-Lipinski "); +MODULE_AUTHOR("Scott K Logan "); +MODULE_DESCRIPTION("xone GIP PDP Jaguar driver"); +MODULE_VERSION("#VERSION#"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/xone/transport/dongle.c b/drivers/hid/xone/transport/dongle.c index 30743e8b4..aa58ac26a 100644 --- a/drivers/hid/xone/transport/dongle.c +++ b/drivers/hid/xone/transport/dongle.c @@ -1,13 +1,13 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2021 Severin von Wnuck + * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #include #include #include +#include #include -#include #include #include @@ -25,6 +25,7 @@ /* autosuspend delay in ms */ #define XONE_DONGLE_SUSPEND_DELAY 60000 +#define XONE_DONGLE_PAIRING_TIMEOUT msecs_to_jiffies(30000) #define XONE_DONGLE_PWR_OFF_TIMEOUT msecs_to_jiffies(5000) enum xone_dongle_queue { @@ -41,6 +42,7 @@ struct xone_dongle_client { struct xone_dongle *dongle; u8 wcid; u8 address[ETH_ALEN]; + bool encryption_enabled; struct gip_adapter *adapter; }; @@ -50,7 +52,8 @@ struct xone_dongle_event { XONE_DONGLE_EVT_ADD_CLIENT, XONE_DONGLE_EVT_REMOVE_CLIENT, XONE_DONGLE_EVT_PAIR_CLIENT, - XONE_DONGLE_EVT_TOGGLE_PAIRING, + XONE_DONGLE_EVT_ENABLE_PAIRING, + XONE_DONGLE_EVT_ENABLE_ENCRYPTION, } type; struct xone_dongle *dongle; @@ -70,6 +73,7 @@ struct xone_dongle { /* serializes pairing changes */ struct mutex pairing_lock; + struct delayed_work pairing_work; bool pairing; /* serializes access to clients array */ @@ -96,6 +100,11 @@ static void xone_dongle_prep_packet(struct xone_dongle_client *client, hdr.frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA | IEEE80211_STYPE_QOS_DATA | IEEE80211_FCTL_FROMDS); + + /* encrypt frame on transmission */ + if (client->encryption_enabled) + hdr.frame_control |= cpu_to_le16(IEEE80211_FCTL_PROTECTED); + hdr.duration_id = cpu_to_le16(144); memcpy(hdr.addr1, client->address, ETH_ALEN); memcpy(hdr.addr2, client->dongle->mt.address, ETH_ALEN); @@ -106,6 +115,7 @@ static void xone_dongle_prep_packet(struct xone_dongle_client *client, IEEE80211_HT_MPDU_DENSITY_4)); txwi.rate = cpu_to_le16(FIELD_PREP(MT_RXWI_RATE_PHY, MT_PHY_TYPE_OFDM)); txwi.ack_ctl = MT_TXWI_ACK_CTL_REQ; + txwi.wcid = client->wcid - 1; txwi.len_ctl = cpu_to_le16(sizeof(hdr) + skb->len); memset(skb_push(skb, 2), 0, 2); @@ -183,9 +193,19 @@ static int xone_dongle_submit_buffer(struct gip_adapter *adap, return err; } +static int xone_dongle_set_encryption_key(struct gip_adapter *adap, + u8 *key, int len) +{ + struct xone_dongle_client *client = dev_get_drvdata(&adap->dev); + + return xone_mt76_set_client_key(&client->dongle->mt, client->wcid, + key, len); +} + static struct gip_adapter_ops xone_dongle_adapter_ops = { .get_buffer = xone_dongle_get_buffer, .submit_buffer = xone_dongle_submit_buffer, + .set_encryption_key = xone_dongle_set_encryption_key, }; static int xone_dongle_toggle_pairing(struct xone_dongle *dongle, bool enable) @@ -196,7 +216,7 @@ static int xone_dongle_toggle_pairing(struct xone_dongle *dongle, bool enable) mutex_lock(&dongle->pairing_lock); - /* pairing is already enabled */ + /* pairing is already enabled/disabled */ if (dongle->pairing == enable) goto err_unlock; @@ -229,47 +249,19 @@ err_unlock: return err; } -static ssize_t xone_dongle_pairing_show(struct device *dev, - struct device_attribute *attr, - char *buf) +static void xone_dongle_pairing_timeout(struct work_struct *work) { - struct usb_interface *intf = to_usb_interface(dev); - struct xone_dongle *dongle = usb_get_intfdata(intf); - - return sprintf(buf, "%d\n", dongle->pairing); -} - -static ssize_t xone_dongle_pairing_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct usb_interface *intf = to_usb_interface(dev); - struct xone_dongle *dongle = usb_get_intfdata(intf); - bool enable; + struct xone_dongle *dongle = container_of(to_delayed_work(work), + typeof(*dongle), + pairing_work); int err; - err = kstrtobool(buf, &enable); + err = xone_dongle_toggle_pairing(dongle, false); if (err) - return err; - - err = xone_dongle_toggle_pairing(dongle, enable); - if (err) - return err; - - return count; + dev_err(dongle->mt.dev, "%s: disable pairing failed: %d\n", + __func__, err); } -static struct device_attribute xone_dongle_attr_pairing = - __ATTR(pairing, 0644, - xone_dongle_pairing_show, - xone_dongle_pairing_store); - -static struct attribute *xone_dongle_attrs[] = { - &xone_dongle_attr_pairing.attr, - NULL, -}; -ATTRIBUTE_GROUPS(xone_dongle); - static struct xone_dongle_client * xone_dongle_create_client(struct xone_dongle *dongle, u8 *addr) { @@ -392,10 +384,35 @@ static int xone_dongle_pair_client(struct xone_dongle *dongle, u8 *addr) return xone_dongle_toggle_pairing(dongle, false); } +static int xone_dongle_enable_client_encryption(struct xone_dongle *dongle, + u8 wcid) +{ + struct xone_dongle_client *client; + u8 data[] = { 0x00, 0x00 }; + int err; + + client = dongle->clients[wcid - 1]; + if (!client) + return -EINVAL; + + dev_dbg(dongle->mt.dev, "%s: wcid=%d, address=%pM\n", + __func__, wcid, client->address); + + err = xone_mt76_send_client_command(&dongle->mt, wcid, client->address, + XONE_MT_CLIENT_ENABLE_ENCRYPTION, + data, sizeof(data)); + if (err) + return err; + + client->encryption_enabled = true; + + return 0; +} + static void xone_dongle_handle_event(struct work_struct *work) { struct xone_dongle_event *evt = container_of(work, typeof(*evt), work); - int err; + int err = 0; switch (evt->type) { case XONE_DONGLE_EVT_ADD_CLIENT: @@ -407,9 +424,15 @@ static void xone_dongle_handle_event(struct work_struct *work) case XONE_DONGLE_EVT_PAIR_CLIENT: err = xone_dongle_pair_client(evt->dongle, evt->address); break; - case XONE_DONGLE_EVT_TOGGLE_PAIRING: + case XONE_DONGLE_EVT_ENABLE_PAIRING: + mod_delayed_work(system_wq, &evt->dongle->pairing_work, + XONE_DONGLE_PAIRING_TIMEOUT); err = xone_dongle_toggle_pairing(evt->dongle, true); break; + case XONE_DONGLE_EVT_ENABLE_ENCRYPTION: + err = xone_dongle_enable_client_encryption(evt->dongle, + evt->wcid); + break; } if (err) @@ -491,21 +514,35 @@ static int xone_dongle_handle_disassociation(struct xone_dongle *dongle, return 0; } -static int xone_dongle_handle_reserved(struct xone_dongle *dongle, - struct sk_buff *skb, u8 *addr) +static int xone_dongle_handle_client_command(struct xone_dongle *dongle, + struct sk_buff *skb, + u8 wcid, u8 *addr) { struct xone_dongle_event *evt; + enum xone_dongle_event_type evt_type; - if (skb->len < 2) + if (skb->len < 2 || skb->data[0] != XONE_MT_WLAN_RESERVED) return -EINVAL; - if (skb->data[1] != 0x01) - return 0; + switch (skb->data[1]) { + case XONE_MT_CLIENT_PAIR_REQ: + evt_type = XONE_DONGLE_EVT_PAIR_CLIENT; + break; + case XONE_MT_CLIENT_ENABLE_ENCRYPTION: + if (!wcid || wcid > XONE_DONGLE_MAX_CLIENTS) + return -EINVAL; - evt = xone_dongle_alloc_event(dongle, XONE_DONGLE_EVT_PAIR_CLIENT); + evt_type = XONE_DONGLE_EVT_ENABLE_ENCRYPTION; + break; + default: + return 0; + } + + evt = xone_dongle_alloc_event(dongle, evt_type); if (!evt) return -ENOMEM; + evt->wcid = wcid; memcpy(evt->address, addr, ETH_ALEN); queue_work(dongle->event_wq, &evt->work); @@ -517,7 +554,7 @@ static int xone_dongle_handle_button(struct xone_dongle *dongle) { struct xone_dongle_event *evt; - evt = xone_dongle_alloc_event(dongle, XONE_DONGLE_EVT_TOGGLE_PAIRING); + evt = xone_dongle_alloc_event(dongle, XONE_DONGLE_EVT_ENABLE_PAIRING); if (!evt) return -ENOMEM; @@ -566,7 +603,8 @@ static int xone_dongle_process_frame(struct xone_dongle *dongle, case IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_DISASSOC: return xone_dongle_handle_disassociation(dongle, wcid); case IEEE80211_FTYPE_MGMT | XONE_MT_WLAN_RESERVED: - return xone_dongle_handle_reserved(dongle, skb, hdr->addr2); + return xone_dongle_handle_client_command(dongle, skb, wcid, + hdr->addr2); } return 0; @@ -835,7 +873,7 @@ static int xone_dongle_power_off_clients(struct xone_dongle *dongle) XONE_DONGLE_PWR_OFF_TIMEOUT)) return -ETIMEDOUT; - return 0; + return xone_dongle_toggle_pairing(dongle, false); } static void xone_dongle_destroy(struct xone_dongle *dongle) @@ -846,6 +884,7 @@ static void xone_dongle_destroy(struct xone_dongle *dongle) usb_kill_anchored_urbs(&dongle->urbs_in_busy); destroy_workqueue(dongle->event_wq); + cancel_delayed_work_sync(&dongle->pairing_work); for (i = 0; i < XONE_DONGLE_MAX_CLIENTS; i++) { client = dongle->clients[i]; @@ -891,19 +930,18 @@ static int xone_dongle_probe(struct usb_interface *intf, return -ENOMEM; mutex_init(&dongle->pairing_lock); + INIT_DELAYED_WORK(&dongle->pairing_work, xone_dongle_pairing_timeout); spin_lock_init(&dongle->clients_lock); init_waitqueue_head(&dongle->disconnect_wait); err = xone_dongle_init(dongle); - if (err) - goto err_destroy_dongle; + if (err) { + xone_dongle_destroy(dongle); + return err; + } usb_set_intfdata(intf, dongle); - err = device_add_groups(&intf->dev, xone_dongle_groups); - if (err) - goto err_destroy_dongle; - /* enable USB remote wakeup and autosuspend */ intf->needs_remote_wakeup = true; device_wakeup_enable(&dongle->mt.udev->dev); @@ -912,11 +950,6 @@ static int xone_dongle_probe(struct usb_interface *intf, usb_enable_autosuspend(dongle->mt.udev); return 0; - -err_destroy_dongle: - xone_dongle_destroy(dongle); - - return err; } static void xone_dongle_disconnect(struct usb_interface *intf) @@ -924,8 +957,6 @@ static void xone_dongle_disconnect(struct usb_interface *intf) struct xone_dongle *dongle = usb_get_intfdata(intf); int err; - device_remove_groups(&intf->dev, xone_dongle_groups); - /* can fail during USB device removal */ err = xone_dongle_power_off_clients(dongle); if (err) @@ -948,7 +979,7 @@ static int xone_dongle_suspend(struct usb_interface *intf, pm_message_t message) usb_kill_anchored_urbs(&dongle->urbs_in_busy); usb_kill_anchored_urbs(&dongle->urbs_out_busy); - flush_workqueue(dongle->event_wq); + cancel_delayed_work_sync(&dongle->pairing_work); return xone_mt76_suspend_radio(&dongle->mt); } @@ -998,7 +1029,11 @@ static struct usb_driver xone_dongle_driver = { .suspend = xone_dongle_suspend, .resume = xone_dongle_resume, .id_table = xone_dongle_id_table, +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0) .drvwrap.driver.shutdown = xone_dongle_shutdown, +#else + .driver.shutdown = xone_dongle_shutdown, +#endif .supports_autosuspend = true, .disable_hub_initiated_lpm = true, .soft_unbind = true, @@ -1007,7 +1042,7 @@ static struct usb_driver xone_dongle_driver = { module_usb_driver(xone_dongle_driver); MODULE_DEVICE_TABLE(usb, xone_dongle_id_table); -MODULE_AUTHOR("Severin von Wnuck "); +MODULE_AUTHOR("Severin von Wnuck-Lipinski "); MODULE_DESCRIPTION("xone dongle driver"); MODULE_VERSION("#VERSION#"); MODULE_LICENSE("GPL"); diff --git a/drivers/hid/xone/transport/mt76.c b/drivers/hid/xone/transport/mt76.c index 2a6f730c0..a72c19e53 100644 --- a/drivers/hid/xone/transport/mt76.c +++ b/drivers/hid/xone/transport/mt76.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2021 Severin von Wnuck + * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #include @@ -30,6 +30,8 @@ #define XONE_MT_CH_5G_LOW 0x01 #define XONE_MT_CH_5G_HIGH 0x02 +#define XONE_MT_WCID_KEY_LEN 16 + /* commands specific to the dongle's firmware */ enum xone_mt76_ms_command { XONE_MT_SET_MAC_ADDRESS = 0x00, @@ -185,7 +187,7 @@ struct sk_buff *xone_mt76_alloc_message(int len, gfp_t gfp) return skb; } -void xone_mt76_prep_message(struct sk_buff *skb, u32 info) +static void xone_mt76_prep_message(struct sk_buff *skb, u32 info) { int len, pad; @@ -545,18 +547,18 @@ err_free_firmware: static const struct xone_mt76_channel xone_mt76_channels[XONE_MT_NUM_CHANNELS] = { - { 0x01, XONE_MT_CH_2G_LOW, MT_PHY_BW_20, 0, true }, - { 0x06, XONE_MT_CH_2G_MID, MT_PHY_BW_20, 0, true }, - { 0x0b, XONE_MT_CH_2G_HIGH, MT_PHY_BW_20, 0, true }, - { 0x24, XONE_MT_CH_5G_LOW, MT_PHY_BW_40, MT_CH_5G_UNII_1, true }, - { 0x28, XONE_MT_CH_5G_LOW, MT_PHY_BW_40, MT_CH_5G_UNII_1, false }, - { 0x2c, XONE_MT_CH_5G_HIGH, MT_PHY_BW_40, MT_CH_5G_UNII_1, true }, - { 0x30, XONE_MT_CH_5G_HIGH, MT_PHY_BW_40, MT_CH_5G_UNII_1, false }, - { 0x95, XONE_MT_CH_5G_LOW, MT_PHY_BW_80, MT_CH_5G_UNII_3, true }, - { 0x99, XONE_MT_CH_5G_LOW, MT_PHY_BW_80, MT_CH_5G_UNII_3, false }, - { 0x9d, XONE_MT_CH_5G_HIGH, MT_PHY_BW_80, MT_CH_5G_UNII_3, true }, - { 0xa1, XONE_MT_CH_5G_HIGH, MT_PHY_BW_80, MT_CH_5G_UNII_3, false }, - { 0xa5, XONE_MT_CH_5G_HIGH, MT_PHY_BW_80, MT_CH_5G_UNII_3, false }, + { 0x01, XONE_MT_CH_2G_LOW, MT_PHY_BW_20, 0, true, 0 }, + { 0x06, XONE_MT_CH_2G_MID, MT_PHY_BW_20, 0, true, 0 }, + { 0x0b, XONE_MT_CH_2G_HIGH, MT_PHY_BW_20, 0, true, 0 }, + { 0x24, XONE_MT_CH_5G_LOW, MT_PHY_BW_40, MT_CH_5G_UNII_1, true, 0 }, + { 0x28, XONE_MT_CH_5G_LOW, MT_PHY_BW_40, MT_CH_5G_UNII_1, false, 0 }, + { 0x2c, XONE_MT_CH_5G_HIGH, MT_PHY_BW_40, MT_CH_5G_UNII_1, true, 0 }, + { 0x30, XONE_MT_CH_5G_HIGH, MT_PHY_BW_40, MT_CH_5G_UNII_1, false, 0 }, + { 0x95, XONE_MT_CH_5G_LOW, MT_PHY_BW_80, MT_CH_5G_UNII_3, true, 0 }, + { 0x99, XONE_MT_CH_5G_LOW, MT_PHY_BW_80, MT_CH_5G_UNII_3, false, 0 }, + { 0x9d, XONE_MT_CH_5G_HIGH, MT_PHY_BW_80, MT_CH_5G_UNII_3, true, 0 }, + { 0xa1, XONE_MT_CH_5G_HIGH, MT_PHY_BW_80, MT_CH_5G_UNII_3, false, 0 }, + { 0xa5, XONE_MT_CH_5G_HIGH, MT_PHY_BW_80, MT_CH_5G_UNII_3, false, 0 }, }; static int xone_mt76_set_channel_candidates(struct xone_mt76 *mt) @@ -1067,13 +1069,11 @@ int xone_mt76_pair_client(struct xone_mt76 *mt, u8 *addr) { struct sk_buff *skb; struct ieee80211_hdr_3addr hdr = {}; - u8 data[] = { - 0x70, 0x02, 0x00, 0x45, 0x55, 0x01, 0x0f, 0x8f, - 0xff, 0x87, 0x1f, - }; + u8 data[] = { 0x00, 0x45, 0x55, 0x01, 0x0f, 0x8f, 0xff, 0x87, 0x1f }; skb = xone_mt76_alloc_message(sizeof(struct mt76_txwi) + sizeof(hdr) + - sizeof(data), GFP_KERNEL); + sizeof(u8) * 2 + sizeof(data), + GFP_KERNEL); if (!skb) return -ENOMEM; @@ -1085,6 +1085,8 @@ int xone_mt76_pair_client(struct xone_mt76 *mt, u8 *addr) skb_reserve(skb, sizeof(struct mt76_txwi)); skb_put_data(skb, &hdr, sizeof(hdr)); + skb_put_u8(skb, XONE_MT_WLAN_RESERVED); + skb_put_u8(skb, XONE_MT_CLIENT_PAIR_RESP); skb_put_data(skb, data, sizeof(data)); return xone_mt76_send_wlan(mt, skb); @@ -1099,7 +1101,7 @@ int xone_mt76_associate_client(struct xone_mt76 *mt, u8 wcid, u8 *addr) sizeof(mgmt.u.assoc_resp); int err; - skb = xone_mt76_alloc_message(sizeof(struct mt76_txwi) + mgmt_len, + skb = xone_mt76_alloc_message(sizeof(struct mt76_txwi) + mgmt_len + 8, GFP_KERNEL); if (!skb) return -ENOMEM; @@ -1135,10 +1137,79 @@ err_free_skb: return err; } +int xone_mt76_send_client_command(struct xone_mt76 *mt, u8 wcid, u8 *addr, + enum xone_mt76_client_command cmd, + u8 *data, int len) +{ + struct sk_buff *skb; + struct mt76_txwi txwi = {}; + struct ieee80211_hdr_3addr hdr = {}; + u8 info[] = { + 0x00, 0x00, 0x00, wcid - 1, 0x00, 0x00, 0x00, 0x00, + }; + + skb = xone_mt76_alloc_message(sizeof(info) + sizeof(txwi) + + sizeof(hdr) + sizeof(u8) * 2 + len, + GFP_KERNEL); + if (!skb) + return -ENOMEM; + + /* wait for acknowledgment */ + txwi.flags = cpu_to_le16(FIELD_PREP(MT_TXWI_FLAGS_MPDU_DENSITY, + IEEE80211_HT_MPDU_DENSITY_4)); + txwi.rate = cpu_to_le16(FIELD_PREP(MT_RXWI_RATE_PHY, MT_PHY_TYPE_OFDM)); + txwi.ack_ctl = MT_TXWI_ACK_CTL_REQ; + txwi.wcid = wcid - 1; + txwi.len_ctl = cpu_to_le16(sizeof(hdr) + sizeof(u8) * 2 + len); + + hdr.frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | + XONE_MT_WLAN_RESERVED); + memcpy(hdr.addr1, addr, ETH_ALEN); + memcpy(hdr.addr2, mt->address, ETH_ALEN); + memcpy(hdr.addr3, mt->address, ETH_ALEN); + + skb_put_data(skb, info, sizeof(info)); + skb_put_data(skb, &txwi, sizeof(txwi)); + skb_put_data(skb, &hdr, sizeof(hdr)); + skb_put_u8(skb, XONE_MT_WLAN_RESERVED); + skb_put_u8(skb, cmd); + + if (data) + skb_put_data(skb, data, len); + + return xone_mt76_send_command(mt, skb, 0); +} + +int xone_mt76_set_client_key(struct xone_mt76 *mt, u8 wcid, u8 *key, int len) +{ + u8 iv[] = { 0x01, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00 }; + __le32 attr = cpu_to_le32(FIELD_PREP(MT_WCID_ATTR_PKEY_MODE, + MT_CIPHER_AES_CCMP) | + MT_WCID_ATTR_PAIRWISE); + int err; + + if (len != XONE_MT_WCID_KEY_LEN) + return -EINVAL; + + err = xone_mt76_write_burst(mt, MT_WCID_KEY(wcid), key, len); + if (err) + return err; + + err = xone_mt76_write_burst(mt, MT_WCID_IV(wcid), iv, sizeof(iv)); + if (err) + return err; + + return xone_mt76_write_burst(mt, MT_WCID_ATTR(wcid), + &attr, sizeof(attr)); +} + int xone_mt76_remove_client(struct xone_mt76 *mt, u8 wcid) { u8 data[] = { wcid - 1, 0x00, 0x00, 0x00 }; u8 addr[ETH_ALEN] = {}; + u8 iv[8] = {}; + u32 attr = 0; + u8 key[XONE_MT_WCID_KEY_LEN] = {}; int err; err = xone_mt76_send_ms_command(mt, XONE_MT_REMOVE_CLIENT, @@ -1146,5 +1217,18 @@ int xone_mt76_remove_client(struct xone_mt76 *mt, u8 wcid) if (err) return err; - return xone_mt76_write_burst(mt, MT_WCID_ADDR(wcid), addr, ETH_ALEN); + err = xone_mt76_write_burst(mt, MT_WCID_ADDR(wcid), addr, sizeof(addr)); + if (err) + return err; + + err = xone_mt76_write_burst(mt, MT_WCID_IV(wcid), iv, sizeof(iv)); + if (err) + return err; + + err = xone_mt76_write_burst(mt, MT_WCID_ATTR(wcid), + &attr, sizeof(attr)); + if (err) + return err; + + return xone_mt76_write_burst(mt, MT_WCID_KEY(wcid), key, sizeof(key)); } diff --git a/drivers/hid/xone/transport/mt76.h b/drivers/hid/xone/transport/mt76.h index 71474adf1..d43e998fd 100644 --- a/drivers/hid/xone/transport/mt76.h +++ b/drivers/hid/xone/transport/mt76.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ /* - * Copyright (C) 2021 Severin von Wnuck + * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #pragma once @@ -30,6 +30,18 @@ enum xone_mt76_event { XONE_MT_EVT_CLIENT_LOST = 0x0e, }; +enum xone_mt76_client_command { + XONE_MT_CLIENT_PAIR_REQ = 0x01, + XONE_MT_CLIENT_PAIR_RESP = 0x02, + XONE_MT_CLIENT_CHANGE_CHAN_REQ = 0x03, + XONE_MT_CLIENT_CHANGE_CHAN_RESP = 0x04, + XONE_MT_CLIENT_STATISTICS_REQ = 0x05, + XONE_MT_CLIENT_STATISTICS_RESP = 0x06, + XONE_MT_CLIENT_SCAN_CHAN_REQ = 0x07, + XONE_MT_CLIENT_SCAN_CHAN_RESP = 0x08, + XONE_MT_CLIENT_ENABLE_ENCRYPTION = 0x10, +}; + struct xone_mt76_channel { u8 index; u8 band; @@ -62,4 +74,8 @@ int xone_mt76_set_pairing(struct xone_mt76 *mt, bool enable); int xone_mt76_pair_client(struct xone_mt76 *mt, u8 *addr); int xone_mt76_associate_client(struct xone_mt76 *mt, u8 wcid, u8 *addr); +int xone_mt76_send_client_command(struct xone_mt76 *mt, u8 wcid, u8 *addr, + enum xone_mt76_client_command cmd, + u8 *data, int len); +int xone_mt76_set_client_key(struct xone_mt76 *mt, u8 wcid, u8 *key, int len); int xone_mt76_remove_client(struct xone_mt76 *mt, u8 wcid); diff --git a/drivers/hid/xone/transport/mt76_defs.h b/drivers/hid/xone/transport/mt76_defs.h index d190ceef8..f5fe65b14 100644 --- a/drivers/hid/xone/transport/mt76_defs.h +++ b/drivers/hid/xone/transport/mt76_defs.h @@ -2,7 +2,7 @@ /* * Based on code from the open source mt76 driver with minor modifications. * - * Copyright (C) 2021 Severin von Wnuck + * Copyright (C) 2021 Severin von Wnuck-Lipinski * * Special thanks to the authors of the mt76 driver: * @@ -1019,6 +1019,18 @@ enum mt76_qsel { MT_QSEL_EDCA_2, }; +enum mt76_cipher_type { + MT_CIPHER_NONE, + MT_CIPHER_WEP40, + MT_CIPHER_WEP104, + MT_CIPHER_TKIP, + MT_CIPHER_AES_CCMP, + MT_CIPHER_CKIP40, + MT_CIPHER_CKIP104, + MT_CIPHER_CKIP128, + MT_CIPHER_WAPI, +}; + struct mt76_fw_header { __le32 ilm_len; __le32 dlm_len; diff --git a/drivers/hid/xone/transport/wired.c b/drivers/hid/xone/transport/wired.c index 9c356e567..e3026b806 100644 --- a/drivers/hid/xone/transport/wired.c +++ b/drivers/hid/xone/transport/wired.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2021 Severin von Wnuck + * Copyright (C) 2021 Severin von Wnuck-Lipinski */ #include @@ -263,8 +263,12 @@ static int xone_wired_submit_buffer(struct gip_adapter *adap, static int xone_wired_enable_audio(struct gip_adapter *adap) { struct xone_wired *wired = dev_get_drvdata(&adap->dev); - struct usb_interface *intf = to_usb_interface(wired->audio_port.dev); + struct usb_interface *intf; + if (!wired->audio_port.dev) + return -ENOTSUPP; + + intf = to_usb_interface(wired->audio_port.dev); if (intf->cur_altsetting->desc.bAlternateSetting == 1) return -EALREADY; @@ -279,6 +283,9 @@ static int xone_wired_init_audio_in(struct gip_adapter *adap) void *buf; int len, i; + if (!port->ep_in) + return -ENOTSUPP; + urb = usb_alloc_urb(XONE_WIRED_NUM_AUDIO_PKTS, GFP_KERNEL); if (!urb) return -ENOMEM; @@ -317,6 +324,9 @@ static int xone_wired_init_audio_out(struct gip_adapter *adap, int pkt_len) void *buf; int i, j; + if (!port->ep_out) + return -ENOTSUPP; + port->buffer_length_out = pkt_len * XONE_WIRED_NUM_AUDIO_PKTS; for (i = 0; i < XONE_WIRED_NUM_AUDIO_URBS; i++) { @@ -356,8 +366,12 @@ static int xone_wired_disable_audio(struct gip_adapter *adap) { struct xone_wired *wired = dev_get_drvdata(&adap->dev); struct xone_wired_port *port = &wired->audio_port; - struct usb_interface *intf = to_usb_interface(port->dev); + struct usb_interface *intf; + if (!port->dev) + return -ENOTSUPP; + + intf = to_usb_interface(port->dev); if (!intf->cur_altsetting->desc.bAlternateSetting) return -EALREADY; @@ -406,14 +420,15 @@ static int xone_wired_init_data_port(struct xone_wired *wired, struct xone_wired_port *port = &wired->data_port; int err; + init_usb_anchor(&port->urbs_out_idle); + init_usb_anchor(&port->urbs_out_busy); + err = usb_find_common_endpoints(intf->cur_altsetting, NULL, NULL, &port->ep_in, &port->ep_out); if (err) return err; port->dev = &intf->dev; - init_usb_anchor(&port->urbs_out_idle); - init_usb_anchor(&port->urbs_out_busy); return 0; } @@ -425,13 +440,18 @@ static int xone_wired_init_audio_port(struct xone_wired *wired) struct usb_host_interface *alt; int err; + init_usb_anchor(&port->urbs_out_idle); + init_usb_anchor(&port->urbs_out_busy); + intf = usb_ifnum_to_if(wired->udev, XONE_WIRED_INTF_AUDIO); - if (!intf) - return -ENODEV; + if (!intf) { + dev_dbg(&wired->udev->dev, "%s: audio unavailable\n", __func__); + return 0; + } alt = usb_altnum_to_altsetting(intf, 1); if (!alt) - return -ENODEV; + return -ENXIO; err = usb_driver_claim_interface(&xone_wired_driver, intf, NULL); if (err) @@ -448,8 +468,6 @@ static int xone_wired_init_audio_port(struct xone_wired *wired) return err; port->dev = &intf->dev; - init_usb_anchor(&port->urbs_out_idle); - init_usb_anchor(&port->urbs_out_busy); return 0; } @@ -540,6 +558,11 @@ static const struct usb_device_id xone_wired_id_table[] = { { XONE_WIRED_VENDOR(0x3285) }, /* Nacon */ { XONE_WIRED_VENDOR(0x2dc8) }, /* 8BitDo */ { XONE_WIRED_VENDOR(0x2e95) }, /* SCUF */ + { XONE_WIRED_VENDOR(0x3537) }, /* GameSir */ + { XONE_WIRED_VENDOR(0x11c1) }, /* ??? */ + { XONE_WIRED_VENDOR(0x294b) }, /* Snakebyte */ + { XONE_WIRED_VENDOR(0x2c16) }, /* Priferential */ + { XONE_WIRED_VENDOR(0x0b05) }, /* ASUS */ { }, }; @@ -553,7 +576,7 @@ static struct usb_driver xone_wired_driver = { module_usb_driver(xone_wired_driver); MODULE_DEVICE_TABLE(usb, xone_wired_id_table); -MODULE_AUTHOR("Severin von Wnuck "); +MODULE_AUTHOR("Severin von Wnuck-Lipinski "); MODULE_DESCRIPTION("xone wired driver"); MODULE_VERSION("#VERSION#"); MODULE_LICENSE("GPL");