mirror of
https://github.com/MiSTer-devel/Linux-Kernel_MiSTer.git
synced 2026-05-17 03:03:57 +00:00
xone: update driver.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
665
drivers/hid/xone/auth/auth.c
Normal file
665
drivers/hid/xone/auth/auth.c
Normal 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);
|
||||
|
||||
49
drivers/hid/xone/auth/auth.h
Normal file
49
drivers/hid/xone/auth/auth.h
Normal 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);
|
||||
290
drivers/hid/xone/auth/crypto.c
Normal file
290
drivers/hid/xone/auth/crypto.c
Normal 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;
|
||||
}
|
||||
21
drivers/hid/xone/auth/crypto.h
Normal file
21
drivers/hid/xone/auth/crypto.h
Normal 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);
|
||||
@@ -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");
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
206
drivers/hid/xone/driver/madcatz_glam.c
Normal file
206
drivers/hid/xone/driver/madcatz_glam.c
Normal 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");
|
||||
203
drivers/hid/xone/driver/madcatz_strat.c
Normal file
203
drivers/hid/xone/driver/madcatz_strat.c
Normal 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");
|
||||
206
drivers/hid/xone/driver/pdp_jaguar.c
Normal file
206
drivers/hid/xone/driver/pdp_jaguar.c
Normal 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");
|
||||
@@ -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");
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user