xone: update driver.

This commit is contained in:
Sorgelig
2026-04-17 18:20:01 +08:00
parent a547c18d0e
commit e2eb39e6f6
22 changed files with 2676 additions and 676 deletions

View File

@@ -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

View File

@@ -0,0 +1,665 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 Severin von Wnuck-Lipinski <severinvonw@outlook.de>
*/
#include <linux/random.h>
#include <crypto/hash.h>
#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);

View File

@@ -0,0 +1,49 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2023 Severin von Wnuck-Lipinski <severinvonw@outlook.de>
*/
#pragma once
#include <linux/types.h>
#include <linux/workqueue.h>
/* 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);

View File

@@ -0,0 +1,290 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2023 Severin von Wnuck-Lipinski <severinvonw@outlook.de>
*/
#include <linux/version.h>
#include <linux/scatterlist.h>
#include <crypto/hash.h>
#include <crypto/sha2.h>
#include <crypto/akcipher.h>
#include <crypto/kpp.h>
#include <crypto/ecdh.h>
#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;
}

View File

@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2023 Severin von Wnuck-Lipinski <severinvonw@outlook.de>
*/
#pragma once
#include <linux/types.h>
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);

View File

@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
* Copyright (C) 2021 Severin von Wnuck-Lipinski <severinvonw@outlook.de>
*/
#include <linux/module.h>
@@ -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 <severinvonw@outlook.de>");
MODULE_DESCRIPTION("xone GIP bus driver");
MODULE_AUTHOR("Severin von Wnuck-Lipinski <severinvonw@outlook.de>");
MODULE_DESCRIPTION("xone GIP driver");
MODULE_VERSION("#VERSION#");
MODULE_LICENSE("GPL");

View File

@@ -1,12 +1,13 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
* Copyright (C) 2021 Severin von Wnuck-Lipinski <severinvonw@outlook.de>
*/
#pragma once
#include <linux/types.h>
#include <linux/device.h>
#include <linux/semaphore.h>
#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,

File diff suppressed because it is too large Load Diff

View File

@@ -1,27 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
* Copyright (C) 2021 Severin von Wnuck-Lipinski <severinvonw@outlook.de>
*/
#pragma once
#include <linux/types.h>
#include <linux/uuid.h>
#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);

View File

@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
* Copyright (C) 2021 Severin von Wnuck-Lipinski <severinvonw@outlook.de>
*/
#include <linux/module.h>
@@ -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 <severinvonw@outlook.de>");
MODULE_AUTHOR("Severin von Wnuck-Lipinski <severinvonw@outlook.de>");
MODULE_DESCRIPTION("xone GIP chatpad driver");
MODULE_VERSION("#VERSION#");
MODULE_LICENSE("GPL");

View File

@@ -1,9 +1,8 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
* Copyright (C) 2021 Severin von Wnuck-Lipinski <severinvonw@outlook.de>
*/
#include <linux/module.h>
#include <linux/sysfs.h>
#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 <severinvonw@outlook.de>");
MODULE_DESCRIPTION("xone GIP common driver");
MODULE_VERSION("#VERSION#");
MODULE_LICENSE("GPL");

View File

@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
* Copyright (C) 2021 Severin von Wnuck-Lipinski <severinvonw@outlook.de>
*/
#pragma once

View File

@@ -1,28 +1,32 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
* Copyright (C) 2021 Severin von Wnuck-Lipinski <severinvonw@outlook.de>
*/
#include <linux/module.h>
#include <linux/version.h>
#include <linux/uuid.h>
#include <linux/timer.h>
#include <linux/input.h>
#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 <severinvonw@outlook.de>");
MODULE_AUTHOR("Severin von Wnuck-Lipinski <severinvonw@outlook.de>");
MODULE_DESCRIPTION("xone GIP gamepad driver");
MODULE_VERSION("#VERSION#");
MODULE_LICENSE("GPL");

View File

@@ -1,17 +1,24 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
* Copyright (C) 2021 Severin von Wnuck-Lipinski <severinvonw@outlook.de>
*/
#include <linux/module.h>
#include <linux/version.h>
#include <linux/hrtimer.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#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 <severinvonw@outlook.de>");
MODULE_AUTHOR("Severin von Wnuck-Lipinski <severinvonw@outlook.de>");
MODULE_DESCRIPTION("xone GIP headset driver");
MODULE_VERSION("#VERSION#");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,206 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2024 Severin von Wnuck-Lipinski <severinvonw@outlook.de>
*/
#include <linux/module.h>
#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 <severinvonw@outlook.de>");
MODULE_DESCRIPTION("xone GIP Mad Catz Drum Kit driver");
MODULE_VERSION("#VERSION#");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,203 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Severin von Wnuck-Lipinski <severinvonw@outlook.de>
*/
#include <linux/module.h>
#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 <severinvonw@outlook.de>");
MODULE_DESCRIPTION("xone GIP Mad Catz Stratocaster driver");
MODULE_VERSION("#VERSION#");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,206 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2022 Severin von Wnuck-Lipinski <severinvonw@outlook.de>
* Copyright (C) 2023 Scott K Logan <logans@cottsay.net>
*/
#include <linux/module.h>
#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 <severinvonw@outlook.de>");
MODULE_AUTHOR("Scott K Logan <logans@cottsay.net>");
MODULE_DESCRIPTION("xone GIP PDP Jaguar driver");
MODULE_VERSION("#VERSION#");
MODULE_LICENSE("GPL");

View File

@@ -1,13 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
* Copyright (C) 2021 Severin von Wnuck-Lipinski <severinvonw@outlook.de>
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/bitfield.h>
#include <linux/version.h>
#include <linux/usb.h>
#include <linux/sysfs.h>
#include <linux/ieee80211.h>
#include <net/cfg80211.h>
@@ -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 <severinvonw@outlook.de>");
MODULE_AUTHOR("Severin von Wnuck-Lipinski <severinvonw@outlook.de>");
MODULE_DESCRIPTION("xone dongle driver");
MODULE_VERSION("#VERSION#");
MODULE_LICENSE("GPL");

View File

@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
* Copyright (C) 2021 Severin von Wnuck-Lipinski <severinvonw@outlook.de>
*/
#include <linux/slab.h>
@@ -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));
}

View File

@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
* Copyright (C) 2021 Severin von Wnuck-Lipinski <severinvonw@outlook.de>
*/
#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);

View File

@@ -2,7 +2,7 @@
/*
* Based on code from the open source mt76 driver with minor modifications.
*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
* Copyright (C) 2021 Severin von Wnuck-Lipinski <severinvonw@outlook.de>
*
* 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;

View File

@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
* Copyright (C) 2021 Severin von Wnuck-Lipinski <severinvonw@outlook.de>
*/
#include <linux/module.h>
@@ -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 <severinvonw@outlook.de>");
MODULE_AUTHOR("Severin von Wnuck-Lipinski <severinvonw@outlook.de>");
MODULE_DESCRIPTION("xone wired driver");
MODULE_VERSION("#VERSION#");
MODULE_LICENSE("GPL");