Add xone (XBox wireless adapter) driver.

This commit is contained in:
Sorgelig
2022-03-14 18:53:41 +08:00
parent adbaaea913
commit 4ddd8ec3de
19 changed files with 7136 additions and 0 deletions

View File

@@ -2691,6 +2691,7 @@ CONFIG_HID_ZEROPLUS=y
# CONFIG_HID_SENSOR_HUB is not set
# CONFIG_HID_ALPS is not set
# CONFIG_HID_MCP2221 is not set
CONFIG_JOYSTICK_XONE=m
# end of Special HID drivers
#

View File

@@ -1258,6 +1258,8 @@ config HID_MCP2221
To compile this driver as a module, choose M here: the module
will be called hid-mcp2221.ko.
source "drivers/hid/xone/Kconfig"
endmenu
endif # HID

View File

@@ -152,3 +152,6 @@ obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/
obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/
obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/
obj-$(CONFIG_JOYSTICK_XONE) += xone/

7
drivers/hid/xone/Kconfig Normal file
View File

@@ -0,0 +1,7 @@
#
# XOne wireless adapter
#
config JOYSTICK_XONE
tristate "XOne"
depends on INPUT && INPUT_JOYSTICK

View File

@@ -0,0 +1,8 @@
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-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

389
drivers/hid/xone/bus/bus.c Normal file
View File

@@ -0,0 +1,389 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/idr.h>
#include <linux/version.h>
#include "bus.h"
#define to_gip_adapter(d) container_of(d, struct gip_adapter, dev)
#define to_gip_client(d) container_of(d, struct gip_client, dev)
#define to_gip_driver(d) container_of(d, struct gip_driver, drv)
static DEFINE_IDA(gip_adapter_ida);
static void gip_adapter_release(struct device *dev)
{
kfree(to_gip_adapter(dev));
}
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;
}
}
static int gip_client_uevent(struct device *dev, struct kobj_uevent_env *env)
{
struct gip_client *client = to_gip_client(dev);
struct gip_classes *classes = client->classes;
if (!classes || !classes->count)
return -EINVAL;
return add_uevent_var(env, "MODALIAS=gip:%s", classes->strings[0]);
}
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);
}
static struct device_type gip_client_type = {
.uevent = gip_client_uevent,
.release = gip_client_release,
};
static int gip_bus_match(struct device *dev, struct device_driver *driver)
{
struct gip_client *client;
struct gip_driver *drv;
int i;
if (dev->type != &gip_client_type)
return false;
client = to_gip_client(dev);
drv = to_gip_driver(driver);
for (i = 0; i < client->classes->count; i++)
if (!strcmp(client->classes->strings[i], drv->class))
return true;
return false;
}
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;
if (client->drv)
return 0;
err = drv->probe(client);
if (!err) {
spin_lock_irqsave(&client->lock, flags);
client->drv = drv;
spin_unlock_irqrestore(&client->lock, flags);
}
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;
if (!drv)
return;
spin_lock_irqsave(&client->lock, flags);
client->drv = NULL;
spin_unlock_irqrestore(&client->lock, flags);
drv->remove(client);
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0)
static int gip_bus_remove_compat(struct device *dev)
{
gip_bus_remove(dev);
return 0;
}
#endif
static struct bus_type gip_bus_type = {
.name = "xone-gip",
.match = gip_bus_match,
.probe = gip_bus_probe,
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0)
.remove = gip_bus_remove_compat,
#else
.remove = gip_bus_remove,
#endif
};
struct gip_adapter *gip_create_adapter(struct device *parent,
struct gip_adapter_ops *ops,
int audio_pkts)
{
struct gip_adapter *adap;
int err;
adap = kzalloc(sizeof(*adap), GFP_KERNEL);
if (!adap)
return ERR_PTR(-ENOMEM);
adap->id = ida_simple_get(&gip_adapter_ida, 0, 0, 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) {
err = -ENOMEM;
goto err_remove_ida;
}
adap->dev.parent = parent;
adap->dev.type = &gip_adapter_type;
adap->dev.bus = &gip_bus_type;
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);
if (err)
goto err_destroy_queue;
dev_dbg(&adap->dev, "%s: registered\n", __func__);
return adap;
err_destroy_queue:
destroy_workqueue(adap->state_queue);
err_remove_ida:
ida_simple_remove(&gip_adapter_ida, adap->id);
err_put_device:
put_device(&adap->dev);
return ERR_PTR(err);
}
EXPORT_SYMBOL_GPL(gip_create_adapter);
int gip_power_off_adapter(struct gip_adapter *adap)
{
struct gip_client *client = adap->clients[0];
if (!client)
return 0;
/* power off main client */
return gip_set_power_mode(client, GIP_PWR_OFF);
}
EXPORT_SYMBOL_GPL(gip_power_off_adapter);
void gip_destroy_adapter(struct gip_adapter *adap)
{
struct gip_client *client;
int i;
/* ensure all state changes have been processed */
flush_workqueue(adap->state_queue);
for (i = GIP_MAX_CLIENTS - 1; i >= 0; i--) {
client = adap->clients[i];
if (!client)
continue;
gip_remove_client(client);
adap->clients[i] = NULL;
}
ida_simple_remove(&gip_adapter_ida, adap->id);
destroy_workqueue(adap->state_queue);
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)
{
struct gip_client *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);
device_initialize(&client->dev);
dev_dbg(&client->dev, "%s: initialized\n", __func__);
return client;
}
struct gip_client *gip_get_or_init_client(struct gip_adapter *adap, u8 id)
{
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;
}
void gip_put_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);
}
void gip_free_client_info(struct gip_client *client)
{
int i;
kfree(client->external_commands);
kfree(client->audio_formats);
kfree(client->capabilities_out);
kfree(client->capabilities_in);
if (client->classes)
for (i = 0; i < client->classes->count; i++)
kfree(client->classes->strings[i]);
kfree(client->classes);
kfree(client->interfaces);
kfree(client->hid_descriptor);
client->external_commands = NULL;
client->audio_formats = NULL;
client->capabilities_out = NULL;
client->capabilities_in = NULL;
client->classes = NULL;
client->interfaces = NULL;
client->hid_descriptor = NULL;
}
int __gip_register_driver(struct gip_driver *drv, struct module *owner,
const char *mod_name)
{
drv->drv.name = drv->name;
drv->drv.bus = &gip_bus_type;
drv->drv.owner = owner;
drv->drv.mod_name = mod_name;
return driver_register(&drv->drv);
}
EXPORT_SYMBOL_GPL(__gip_register_driver);
void gip_unregister_driver(struct gip_driver *drv)
{
driver_unregister(&drv->drv);
}
EXPORT_SYMBOL_GPL(gip_unregister_driver);
static int __init gip_bus_init(void)
{
return bus_register(&gip_bus_type);
}
static void __exit gip_bus_exit(void)
{
bus_unregister(&gip_bus_type);
}
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_LICENSE("GPL");

127
drivers/hid/xone/bus/bus.h Normal file
View File

@@ -0,0 +1,127 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
*/
#pragma once
#include <linux/types.h>
#include <linux/device.h>
#include "protocol.h"
#define GIP_MAX_CLIENTS 16
#define gip_register_driver(drv) \
__gip_register_driver(drv, THIS_MODULE, KBUILD_MODNAME)
#define module_gip_driver(drv) \
module_driver(drv, gip_register_driver, gip_unregister_driver)
struct gip_adapter_buffer {
enum gip_adapter_buffer_type {
GIP_BUF_DATA,
GIP_BUF_AUDIO,
} type;
void *context;
void *data;
int length;
};
struct gip_adapter_ops {
int (*get_buffer)(struct gip_adapter *adap,
struct gip_adapter_buffer *buf);
int (*submit_buffer)(struct gip_adapter *adap,
struct gip_adapter_buffer *buf);
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);
int (*disable_audio)(struct gip_adapter *adap);
};
struct gip_adapter {
struct device dev;
int id;
struct gip_adapter_ops *ops;
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;
/* serializes access to data sequence number */
spinlock_t send_lock;
u8 data_sequence;
u8 audio_sequence;
};
struct gip_client {
struct device dev;
u8 id;
atomic_t state;
struct gip_adapter *adapter;
struct gip_driver *drv;
struct gip_chunk_buffer *chunk_buf;
struct gip_hardware hardware;
struct gip_info_element *external_commands;
struct gip_info_element *audio_formats;
struct gip_info_element *capabilities_in;
struct gip_info_element *capabilities_out;
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 (*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);
};
struct gip_driver {
struct device_driver drv;
const char *name;
const char *class;
struct gip_driver_ops ops;
int (*probe)(struct gip_client *client);
void (*remove)(struct gip_client *client);
};
struct gip_adapter *gip_create_adapter(struct device *parent,
struct gip_adapter_ops *ops,
int audio_pkts);
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);
void gip_free_client_info(struct gip_client *client);
int __gip_register_driver(struct gip_driver *drv, struct module *owner,
const char *mod_name);
void gip_unregister_driver(struct gip_driver *drv);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,115 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
*/
#pragma once
#include <linux/types.h>
/* 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 {
GIP_BATT_LEVEL_LOW = 0x00,
GIP_BATT_LEVEL_NORMAL = 0x01,
GIP_BATT_LEVEL_HIGH = 0x02,
GIP_BATT_LEVEL_FULL = 0x03,
};
enum gip_power_mode {
GIP_PWR_ON = 0x00,
GIP_PWR_SLEEP = 0x01,
GIP_PWR_OFF = 0x04,
GIP_PWR_RESET = 0x07,
};
enum gip_audio_format {
GIP_AUD_FORMAT_24KHZ_MONO = 0x09,
GIP_AUD_FORMAT_48KHZ_STEREO = 0x10,
};
enum gip_audio_format_chat {
GIP_AUD_FORMAT_CHAT_24KHZ = 0x04,
GIP_AUD_FORMAT_CHAT_16KHZ = 0x05,
};
enum gip_led_mode {
GIP_LED_OFF = 0x00,
GIP_LED_ON = 0x01,
GIP_LED_BLINK_FAST = 0x02,
GIP_LED_BLINK_NORMAL = 0x03,
GIP_LED_BLINK_SLOW = 0x04,
GIP_LED_FADE_SLOW = 0x08,
GIP_LED_FADE_FAST = 0x09,
};
struct gip_chunk_buffer {
bool full;
u16 length;
u8 data[];
};
struct gip_hardware {
u16 vendor;
u16 product;
u16 version;
};
struct gip_info_element {
u8 count;
u8 data[];
};
struct gip_audio_config {
enum gip_audio_format format;
int channels;
int sample_rate;
int buffer_size;
int fragment_size;
int packet_size;
bool valid;
};
struct gip_classes {
u8 count;
const char *strings[];
};
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_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);
int gip_send_rumble(struct gip_client *client, void *pkt, u8 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);
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);
void gip_disable_audio(struct gip_client *client);
int gip_process_buffer(struct gip_adapter *adap, void *data, int len);

View File

@@ -0,0 +1,205 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
*/
#include <linux/module.h>
#include <linux/hid.h>
#include "common.h"
#define GIP_CP_NAME "Microsoft X-Box One chatpad"
struct gip_chatpad {
struct gip_client *client;
struct gip_input input;
struct hid_device *hid_dev;
};
static int gip_chatpad_hid_start(struct hid_device *dev)
{
return 0;
}
static void gip_chatpad_hid_stop(struct hid_device *dev)
{
}
static int gip_chatpad_hid_open(struct hid_device *dev)
{
return 0;
}
static void gip_chatpad_hid_close(struct hid_device *dev)
{
}
static int gip_chatpad_hid_parse(struct hid_device *dev)
{
struct gip_chatpad *chatpad = dev->driver_data;
struct gip_client *client = chatpad->client;
struct gip_info_element *desc_info = client->hid_descriptor;
struct hid_descriptor *desc = (struct hid_descriptor *)desc_info->data;
if (desc->bLength < sizeof(*desc) || desc->bNumDescriptors != 1) {
dev_err(&client->dev, "%s: invalid descriptor\n", __func__);
return -EINVAL;
}
dev->version = le16_to_cpu(desc->bcdHID);
dev->country = desc->bCountryCode;
return hid_parse_report(dev, desc_info->data + sizeof(*desc),
desc_info->count - sizeof(*desc));
}
static int gip_chatpad_hid_raw_request(struct hid_device *dev,
unsigned char report_num, __u8 *buf,
size_t len, unsigned char report_type,
int request_type)
{
return 0;
}
static struct hid_ll_driver gip_chatpad_hid_driver = {
.start = gip_chatpad_hid_start,
.stop = gip_chatpad_hid_stop,
.open = gip_chatpad_hid_open,
.close = gip_chatpad_hid_close,
.parse = gip_chatpad_hid_parse,
.raw_request = gip_chatpad_hid_raw_request,
};
static int gip_chatpad_init_input(struct gip_chatpad *chatpad)
{
int err;
input_set_capability(chatpad->input.dev, EV_KEY, BTN_MODE);
err = input_register_device(chatpad->input.dev);
if (err)
dev_err(&chatpad->client->dev, "%s: register failed: %d\n",
__func__, err);
return err;
}
static int gip_chatpad_init_hid(struct gip_chatpad *chatpad)
{
struct gip_client *client = chatpad->client;
struct hid_device *dev;
int err;
dev = hid_allocate_device();
if (IS_ERR(dev)) {
dev_err(&client->dev, "%s: allocate failed: %ld\n",
__func__, PTR_ERR(dev));
return PTR_ERR(dev);
}
dev->bus = BUS_USB;
dev->vendor = client->hardware.vendor;
dev->product = client->hardware.product;
dev->version = client->hardware.version;
dev->dev.parent = &client->dev;
dev->ll_driver = &gip_chatpad_hid_driver;
strscpy(dev->name, GIP_CP_NAME, sizeof(dev->name));
snprintf(dev->phys, sizeof(dev->phys), "%s/input1",
dev_name(&client->dev));
dev->driver_data = chatpad;
err = hid_add_device(dev);
if (err) {
dev_err(&client->dev, "%s: add failed: %d\n", __func__, err);
hid_destroy_device(dev);
return err;
}
chatpad->hid_dev = dev;
return 0;
}
static int gip_chatpad_op_guide_button(struct gip_client *client, bool down)
{
struct gip_chatpad *chatpad = dev_get_drvdata(&client->dev);
input_report_key(chatpad->input.dev, BTN_MODE, down);
input_sync(chatpad->input.dev);
return 0;
}
static int gip_chatpad_op_hid_report(struct gip_client *client,
void *data, int len)
{
struct gip_chatpad *chatpad = dev_get_drvdata(&client->dev);
return hid_input_report(chatpad->hid_dev, HID_INPUT_REPORT,
data, len, true);
}
static int gip_chatpad_probe(struct gip_client *client)
{
struct gip_chatpad *chatpad;
struct gip_info_element *hid_desc = client->hid_descriptor;
int err;
if (!hid_desc || hid_desc->count < sizeof(struct hid_descriptor))
return -ENODEV;
chatpad = devm_kzalloc(&client->dev, sizeof(*chatpad), GFP_KERNEL);
if (!chatpad)
return -ENOMEM;
chatpad->client = client;
err = gip_init_input(&chatpad->input, client, GIP_CP_NAME);
if (err)
return err;
err = gip_chatpad_init_input(chatpad);
if (err)
return err;
err = gip_chatpad_init_hid(chatpad);
if (err)
return err;
err = gip_set_power_mode(client, GIP_PWR_ON);
if (err)
return err;
dev_set_drvdata(&client->dev, chatpad);
return 0;
}
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 = {
.name = "xone-gip-chatpad",
.class = "Windows.Xbox.Input.Chatpad",
.ops = {
.guide_button = gip_chatpad_op_guide_button,
.hid_report = gip_chatpad_op_hid_report,
},
.probe = gip_chatpad_probe,
.remove = gip_chatpad_remove,
};
module_gip_driver(gip_chatpad_driver);
MODULE_ALIAS("gip:Windows.Xbox.Input.Chatpad");
MODULE_AUTHOR("Severin von Wnuck <severinvonw@outlook.de>");
MODULE_DESCRIPTION("xone GIP chatpad driver");
MODULE_VERSION("#VERSION#");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,225 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
*/
#include <linux/module.h>
#include <linux/sysfs.h>
#include "common.h"
#define GIP_LED_BRIGHTNESS_DEFAULT 20
#define GIP_LED_BRIGHTNESS_MAX 50
static enum power_supply_property gip_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_SCOPE,
POWER_SUPPLY_PROP_MODEL_NAME,
};
static int gip_get_battery_prop(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct gip_battery *batt = power_supply_get_drvdata(psy);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = batt->status;
break;
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
val->intval = batt->capacity;
break;
case POWER_SUPPLY_PROP_SCOPE:
val->intval = POWER_SUPPLY_SCOPE_DEVICE;
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = batt->name;
break;
default:
return -EINVAL;
}
return 0;
}
int gip_init_battery(struct gip_battery *batt, struct gip_client *client,
const char *name)
{
struct power_supply_config cfg = {};
batt->name = name;
batt->status = POWER_SUPPLY_STATUS_UNKNOWN;
batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
batt->desc.name = dev_name(&client->dev);
batt->desc.type = POWER_SUPPLY_TYPE_BATTERY;
batt->desc.properties = gip_battery_props;
batt->desc.num_properties = ARRAY_SIZE(gip_battery_props);
batt->desc.get_property = gip_get_battery_prop;
cfg.drv_data = batt;
batt->supply = devm_power_supply_register(&client->dev, &batt->desc,
&cfg);
if (IS_ERR(batt->supply)) {
dev_err(&client->dev, "%s: register failed: %ld\n",
__func__, PTR_ERR(batt->supply));
return PTR_ERR(batt->supply);
}
power_supply_powers(batt->supply, &client->dev);
return 0;
}
EXPORT_SYMBOL_GPL(gip_init_battery);
void gip_report_battery(struct gip_battery *batt,
enum gip_battery_type type,
enum gip_battery_level level)
{
if (type == GIP_BATT_TYPE_NONE)
batt->status = POWER_SUPPLY_STATUS_NOT_CHARGING;
else
batt->status = POWER_SUPPLY_STATUS_DISCHARGING;
if (type == GIP_BATT_TYPE_NONE)
batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
else if (level == GIP_BATT_LEVEL_LOW)
batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
else if (level == GIP_BATT_LEVEL_NORMAL)
batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
else if (level == GIP_BATT_LEVEL_HIGH)
batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_HIGH;
else if (level == GIP_BATT_LEVEL_FULL)
batt->capacity = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
if (batt->supply)
power_supply_changed(batt->supply);
}
EXPORT_SYMBOL_GPL(gip_report_battery);
static void gip_led_brightness_set(struct led_classdev *dev,
enum led_brightness brightness)
{
struct gip_led *led = container_of(dev, typeof(*led), dev);
int err;
if (dev->flags & LED_UNREGISTERING)
return;
dev_dbg(&led->client->dev, "%s: brightness=%d\n", __func__, brightness);
err = gip_set_led_mode(led->client, led->mode, brightness);
if (err)
dev_err(&led->client->dev, "%s: set LED mode failed: %d\n",
__func__, err);
}
static ssize_t gip_led_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *cdev = dev_get_drvdata(dev);
struct gip_led *led = container_of(cdev, typeof(*led), dev);
return sprintf(buf, "%u\n", led->mode);
}
static ssize_t gip_led_mode_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct led_classdev *cdev = dev_get_drvdata(dev);
struct gip_led *led = container_of(cdev, typeof(*led), dev);
u8 mode;
int err;
err = kstrtou8(buf, 10, &mode);
if (err)
return err;
dev_dbg(&led->client->dev, "%s: mode=%u\n", __func__, mode);
led->mode = mode;
err = gip_set_led_mode(led->client, mode, cdev->brightness);
if (err) {
dev_err(&led->client->dev, "%s: set LED mode failed: %d\n",
__func__, err);
return err;
}
return count;
}
static struct device_attribute gip_led_attr_mode =
__ATTR(mode, 0644, gip_led_mode_show, gip_led_mode_store);
static struct attribute *gip_led_attrs[] = {
&gip_led_attr_mode.attr,
NULL,
};
ATTRIBUTE_GROUPS(gip_led);
int gip_init_led(struct gip_led *led, struct gip_client *client)
{
int err;
/* set default brightness */
err = gip_set_led_mode(client, GIP_LED_ON, GIP_LED_BRIGHTNESS_DEFAULT);
if (err) {
dev_err(&client->dev, "%s: set brightness failed: %d\n",
__func__, err);
return err;
}
led->dev.name = devm_kasprintf(&client->dev, GFP_KERNEL,
"%s:white:status",
dev_name(&client->dev));
if (!led->dev.name)
return -ENOMEM;
led->dev.brightness = GIP_LED_BRIGHTNESS_DEFAULT;
led->dev.max_brightness = GIP_LED_BRIGHTNESS_MAX;
led->dev.brightness_set = gip_led_brightness_set;
led->dev.groups = gip_led_groups;
led->client = client;
led->mode = GIP_LED_ON;
err = devm_led_classdev_register(&client->dev, &led->dev);
if (err)
dev_err(&client->dev, "%s: register failed: %d\n",
__func__, err);
return err;
}
EXPORT_SYMBOL_GPL(gip_init_led);
int gip_init_input(struct gip_input *input, struct gip_client *client,
const char *name)
{
input->dev = devm_input_allocate_device(&client->dev);
if (!input->dev)
return -ENOMEM;
input->dev->phys = devm_kasprintf(&client->dev, GFP_KERNEL,
"%s/input0", dev_name(&client->dev));
if (!input->dev->phys)
return -ENOMEM;
input->dev->name = name;
input->dev->id.bustype = BUS_VIRTUAL;
input->dev->id.vendor = client->hardware.vendor;
input->dev->id.product = client->hardware.product;
input->dev->id.version = client->hardware.version;
input->dev->dev.parent = &client->dev;
return 0;
}
EXPORT_SYMBOL_GPL(gip_init_input);
MODULE_AUTHOR("Severin von Wnuck <severinvonw@outlook.de>");
MODULE_DESCRIPTION("xone GIP common driver");
MODULE_VERSION("#VERSION#");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,43 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
*/
#pragma once
#include <linux/power_supply.h>
#include <linux/leds.h>
#include <linux/input.h>
#include "../bus/bus.h"
struct gip_battery {
struct power_supply *supply;
struct power_supply_desc desc;
const char *name;
int status;
int capacity;
};
struct gip_led {
struct led_classdev dev;
struct gip_client *client;
enum gip_led_mode mode;
};
struct gip_input {
struct input_dev *dev;
};
int gip_init_battery(struct gip_battery *batt, struct gip_client *client,
const char *name);
void gip_report_battery(struct gip_battery *batt,
enum gip_battery_type type,
enum gip_battery_level level);
int gip_init_led(struct gip_led *led, struct gip_client *client);
int gip_init_input(struct gip_input *input, struct gip_client *client,
const char *name);

View File

@@ -0,0 +1,338 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
*/
#include <linux/module.h>
#include <linux/uuid.h>
#include <linux/timer.h>
#include <linux/input.h>
#include "common.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_RUMBLE_DELAY msecs_to_jiffies(10)
#define GIP_GP_RUMBLE_MAX 100
static const guid_t gip_gamepad_guid_middle_button =
GUID_INIT(0xecddd2fe, 0xd387, 0x4294,
0xbd, 0x96, 0x1a, 0x71, 0x2e, 0x3d, 0xc7, 0x7d);
enum gip_gamepad_button {
GIP_GP_BTN_MENU = BIT(2),
GIP_GP_BTN_VIEW = BIT(3),
GIP_GP_BTN_A = BIT(4),
GIP_GP_BTN_B = BIT(5),
GIP_GP_BTN_X = BIT(6),
GIP_GP_BTN_Y = BIT(7),
GIP_GP_BTN_DPAD_U = BIT(8),
GIP_GP_BTN_DPAD_D = BIT(9),
GIP_GP_BTN_DPAD_L = BIT(10),
GIP_GP_BTN_DPAD_R = BIT(11),
GIP_GP_BTN_BUMPER_L = BIT(12),
GIP_GP_BTN_BUMPER_R = BIT(13),
GIP_GP_BTN_STICK_L = BIT(14),
GIP_GP_BTN_STICK_R = BIT(15),
};
enum gip_gamepad_motor {
GIP_GP_MOTOR_R = BIT(0),
GIP_GP_MOTOR_L = BIT(1),
GIP_GP_MOTOR_RT = BIT(2),
GIP_GP_MOTOR_LT = BIT(3),
};
struct gip_gamepad_pkt_input {
__le16 buttons;
__le16 trigger_left;
__le16 trigger_right;
__le16 stick_left_x;
__le16 stick_left_y;
__le16 stick_right_x;
__le16 stick_right_y;
} __packed;
struct gip_gamepad_pkt_series_xs {
u8 unknown[4];
u8 share_button;
} __packed;
struct gip_gamepad_pkt_rumble {
u8 unknown;
u8 motors;
u8 left_trigger;
u8 right_trigger;
u8 left;
u8 right;
u8 duration;
u8 delay;
u8 repeat;
} __packed;
struct gip_gamepad {
struct gip_client *client;
struct gip_battery battery;
struct gip_led led;
struct gip_input input;
bool series_xs;
struct gip_gamepad_rumble {
/* serializes access to rumble packet */
spinlock_t lock;
bool queued;
unsigned long last;
struct timer_list timer;
struct gip_gamepad_pkt_rumble pkt;
} rumble;
};
static void gip_gamepad_send_rumble(struct timer_list *timer)
{
struct gip_gamepad_rumble *rumble = from_timer(rumble, timer, timer);
struct gip_gamepad *gamepad = container_of(rumble, typeof(*gamepad),
rumble);
unsigned long flags;
spin_lock_irqsave(&rumble->lock, flags);
gip_send_rumble(gamepad->client, &rumble->pkt, sizeof(rumble->pkt));
rumble->last = jiffies;
spin_unlock_irqrestore(&rumble->lock, flags);
}
static int gip_gamepad_queue_rumble(struct input_dev *dev, void *data,
struct ff_effect *effect)
{
struct gip_gamepad_rumble *rumble = input_get_drvdata(dev);
u32 mag_left = effect->u.rumble.strong_magnitude;
u32 mag_right = effect->u.rumble.weak_magnitude;
unsigned long flags;
if (effect->type != FF_RUMBLE)
return 0;
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))
mod_timer(&rumble->timer, rumble->last + GIP_GP_RUMBLE_DELAY);
spin_unlock_irqrestore(&rumble->lock, flags);
return 0;
}
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)
input_set_capability(dev, EV_KEY, KEY_RECORD);
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_TL);
input_set_capability(dev, EV_KEY, BTN_TR);
input_set_capability(dev, EV_KEY, BTN_THUMBL);
input_set_capability(dev, EV_KEY, BTN_THUMBR);
input_set_capability(dev, EV_FF, FF_RUMBLE);
input_set_abs_params(dev, ABS_X, -32768, 32767, 16, 128);
input_set_abs_params(dev, ABS_RX, -32768, 32767, 16, 128);
input_set_abs_params(dev, ABS_Y, -32768, 32767, 16, 128);
input_set_abs_params(dev, ABS_RY, -32768, 32767, 16, 128);
input_set_abs_params(dev, ABS_Z, 0, 1023, 0, 0);
input_set_abs_params(dev, ABS_RZ, 0, 1023, 0, 0);
input_set_abs_params(dev, ABS_HAT0X, -1, 1, 0, 0);
input_set_abs_params(dev, ABS_HAT0Y, -1, 1, 0, 0);
input_set_drvdata(dev, &gamepad->rumble);
err = input_ff_create_memless(dev, NULL, gip_gamepad_queue_rumble);
if (err) {
dev_err(&gamepad->client->dev, "%s: create FF failed: %d\n",
__func__, err);
return err;
}
err = input_register_device(dev);
if (err) {
dev_err(&gamepad->client->dev, "%s: register failed: %d\n",
__func__, err);
return err;
}
spin_lock_init(&gamepad->rumble.lock);
timer_setup(&gamepad->rumble.timer, gip_gamepad_send_rumble, 0);
return 0;
}
static int gip_gamepad_op_battery(struct gip_client *client,
enum gip_battery_type type,
enum gip_battery_level level)
{
struct gip_gamepad *gamepad = dev_get_drvdata(&client->dev);
gip_report_battery(&gamepad->battery, type, level);
return 0;
}
static int gip_gamepad_op_guide_button(struct gip_client *client, bool down)
{
struct gip_gamepad *gamepad = dev_get_drvdata(&client->dev);
input_report_key(gamepad->input.dev, BTN_MODE, down);
input_sync(gamepad->input.dev);
return 0;
}
static int gip_gamepad_op_input(struct gip_client *client, void *data, int 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);
if (len < sizeof(*pkt))
return -EINVAL;
if (gamepad->series_xs) {
if (len < sizeof(*pkt) + sizeof(*pkt_xs))
return -EINVAL;
input_report_key(dev, KEY_RECORD, !!pkt_xs->share_button);
}
input_report_key(dev, BTN_START, buttons & GIP_GP_BTN_MENU);
input_report_key(dev, BTN_SELECT, buttons & GIP_GP_BTN_VIEW);
input_report_key(dev, BTN_A, buttons & GIP_GP_BTN_A);
input_report_key(dev, BTN_B, buttons & GIP_GP_BTN_B);
input_report_key(dev, BTN_X, buttons & GIP_GP_BTN_X);
input_report_key(dev, BTN_Y, buttons & GIP_GP_BTN_Y);
input_report_key(dev, BTN_TL, buttons & GIP_GP_BTN_BUMPER_L);
input_report_key(dev, BTN_TR, buttons & GIP_GP_BTN_BUMPER_R);
input_report_key(dev, BTN_THUMBL, buttons & GIP_GP_BTN_STICK_L);
input_report_key(dev, BTN_THUMBR, buttons & GIP_GP_BTN_STICK_R);
input_report_abs(dev, ABS_X, (s16)le16_to_cpu(pkt->stick_left_x));
input_report_abs(dev, ABS_RX, (s16)le16_to_cpu(pkt->stick_right_x));
input_report_abs(dev, ABS_Y, ~(s16)le16_to_cpu(pkt->stick_left_y));
input_report_abs(dev, ABS_RY, ~(s16)le16_to_cpu(pkt->stick_right_y));
input_report_abs(dev, ABS_Z, le16_to_cpu(pkt->trigger_left));
input_report_abs(dev, ABS_RZ, le16_to_cpu(pkt->trigger_right));
input_report_abs(dev, ABS_HAT0X, !!(buttons & GIP_GP_BTN_DPAD_R) -
!!(buttons & GIP_GP_BTN_DPAD_L));
input_report_abs(dev, ABS_HAT0Y, !!(buttons & GIP_GP_BTN_DPAD_D) -
!!(buttons & GIP_GP_BTN_DPAD_U));
input_sync(dev);
return 0;
}
static int gip_gamepad_probe(struct gip_client *client)
{
struct gip_gamepad *gamepad;
int err;
gamepad = devm_kzalloc(&client->dev, sizeof(*gamepad), GFP_KERNEL);
if (!gamepad)
return -ENOMEM;
gamepad->client = client;
err = gip_init_input(&gamepad->input, client, GIP_GP_NAME);
if (err)
return err;
err = gip_gamepad_init_input(gamepad);
if (err)
return err;
err = gip_init_battery(&gamepad->battery, client, GIP_GP_NAME);
if (err)
return err;
err = gip_set_power_mode(client, GIP_PWR_ON);
if (err)
return err;
err = gip_init_led(&gamepad->led, client);
if (err)
return err;
err = gip_complete_authentication(client);
if (err)
return err;
dev_set_drvdata(&client->dev, gamepad);
return 0;
}
static void gip_gamepad_remove(struct gip_client *client)
{
struct gip_gamepad *gamepad = dev_get_drvdata(&client->dev);
del_timer_sync(&gamepad->rumble.timer);
dev_set_drvdata(&client->dev, NULL);
}
static struct gip_driver gip_gamepad_driver = {
.name = "xone-gip-gamepad",
.class = "Windows.Xbox.Input.Gamepad",
.ops = {
.battery = gip_gamepad_op_battery,
.guide_button = gip_gamepad_op_guide_button,
.input = gip_gamepad_op_input,
},
.probe = gip_gamepad_probe,
.remove = gip_gamepad_remove,
};
module_gip_driver(gip_gamepad_driver);
MODULE_ALIAS("gip:Windows.Xbox.Input.Gamepad");
MODULE_AUTHOR("Severin von Wnuck <severinvonw@outlook.de>");
MODULE_DESCRIPTION("xone GIP gamepad driver");
MODULE_VERSION("#VERSION#");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,498 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
*/
#include <linux/module.h>
#include <linux/hrtimer.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include "../bus/bus.h"
#define GIP_HS_NAME "Microsoft X-Box One headset"
#define GIP_HS_CONFIG_DELAY msecs_to_jiffies(1000)
#define GIP_HS_POWER_ON_DELAY msecs_to_jiffies(1000)
static const struct snd_pcm_hardware gip_headset_pcm_hw = {
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_BATCH |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
.periods_min = 2,
.periods_max = 1024,
};
struct gip_headset {
struct gip_client *client;
struct delayed_work config_work;
struct delayed_work power_on_work;
struct work_struct register_work;
bool registered;
struct hrtimer timer;
void *buffer;
struct gip_headset_stream {
struct snd_pcm_substream *substream;
snd_pcm_uframes_t pointer;
snd_pcm_uframes_t period;
} playback, capture;
struct snd_card *card;
struct snd_pcm *pcm;
};
static int gip_headset_pcm_open(struct snd_pcm_substream *sub)
{
struct gip_headset *headset = snd_pcm_substream_chip(sub);
struct gip_audio_config *cfg;
struct snd_pcm_hardware hw = gip_headset_pcm_hw;
if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
cfg = &headset->client->audio_config_out;
else
cfg = &headset->client->audio_config_in;
hw.rate_min = cfg->sample_rate;
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.period_bytes_min = cfg->buffer_size;
hw.period_bytes_max = cfg->buffer_size * 8;
sub->runtime->hw = hw;
return 0;
}
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;
}
static int gip_headset_pcm_trigger(struct snd_pcm_substream *sub, int cmd)
{
struct gip_headset *headset = snd_pcm_substream_chip(sub);
struct gip_headset_stream *stream;
if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
stream = &headset->playback;
else
stream = &headset->capture;
stream->pointer = 0;
stream->period = 0;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
stream->substream = sub;
break;
case SNDRV_PCM_TRIGGER_STOP:
stream->substream = NULL;
break;
default:
return -EINVAL;
}
if (!stream->substream && sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
memset(headset->buffer, 0,
headset->client->audio_config_out.buffer_size);
return 0;
}
static snd_pcm_uframes_t gip_headset_pcm_pointer(struct snd_pcm_substream *sub)
{
struct gip_headset *headset = snd_pcm_substream_chip(sub);
struct gip_headset_stream *stream;
if (sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
stream = &headset->playback;
else
stream = &headset->capture;
return bytes_to_frames(sub->runtime, stream->pointer);
}
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)
{
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);
}
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;
return true;
}
return false;
}
static bool gip_headset_copy_capture(struct gip_headset_stream *stream,
unsigned char *data, int len)
{
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(runtime->dma_area + stream->pointer, data, len);
} else {
memcpy(runtime->dma_area + stream->pointer, data, remaining);
memcpy(runtime->dma_area, data + remaining, len - remaining);
}
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;
return true;
}
return false;
}
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;
bool elapsed = false;
int err;
unsigned long flags;
if (sub) {
snd_pcm_stream_lock_irqsave(sub, flags);
if (sub->runtime && snd_pcm_running(sub))
elapsed = gip_headset_copy_playback(stream,
headset->buffer,
cfg->buffer_size);
snd_pcm_stream_unlock_irqrestore(sub, flags);
if (elapsed)
snd_pcm_period_elapsed(sub);
}
/* retry if driver runs out of buffers */
err = gip_send_audio_samples(headset->client, headset->buffer);
if (err && err != -ENOSPC)
return HRTIMER_NORESTART;
hrtimer_forward_now(timer, ms_to_ktime(GIP_AUDIO_INTERVAL));
return HRTIMER_RESTART;
}
static int gip_headset_init_card(struct gip_headset *headset)
{
struct snd_card *card;
int err;
err = snd_card_new(&headset->client->dev, SNDRV_DEFAULT_IDX1,
SNDRV_DEFAULT_STR1, THIS_MODULE, 0, &card);
if (err)
return err;
strscpy(card->driver, "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);
if (err)
return err;
strscpy(pcm->name, "GIP Headset", 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);
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;
}
static void gip_headset_config(struct work_struct *work)
{
struct gip_headset *headset = container_of(to_delayed_work(work),
typeof(*headset),
config_work);
struct gip_client *client = headset->client;
struct gip_info_element *fmts = client->audio_formats;
int err;
dev_dbg(&client->dev, "%s: format=0x%02x/0x%02x\n", __func__,
fmts->data[0], fmts->data[1]);
/* suggest initial audio format */
err = gip_suggest_audio_format(client, fmts->data[0], fmts->data[1]);
if (err)
dev_err(&client->dev, "%s: suggest format failed: %d\n",
__func__, err);
}
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);
struct gip_client *client = headset->client;
int err;
err = gip_set_power_mode(client, GIP_PWR_ON);
if (err)
dev_err(&client->dev, "%s: set power mode 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;
int err;
err = gip_headset_init_card(headset);
if (err) {
dev_err(dev, "%s: init card failed: %d\n", __func__, err);
return;
}
err = gip_headset_init_pcm(headset);
if (err) {
dev_err(dev, "%s: init PCM failed: %d\n", __func__, err);
goto err_free_card;
}
err = gip_headset_start_audio(headset);
if (err) {
dev_err(dev, "%s: start audio failed: %d\n", __func__, err);
goto err_free_card;
}
return;
err_free_card:
snd_card_free(headset->card);
headset->card = NULL;
}
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);
return 0;
}
static int gip_headset_op_audio_volume(struct gip_client *client,
int in, int 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);
headset->registered = true;
}
/* ignore hardware volume, let software handle volume changes */
return 0;
}
static int gip_headset_op_audio_samples(struct gip_client *client,
void *data, int len)
{
struct gip_headset *headset = dev_get_drvdata(&client->dev);
struct gip_headset_stream *stream = &headset->capture;
struct snd_pcm_substream *sub = stream->substream;
bool elapsed = false;
unsigned long flags;
if (!sub)
return 0;
snd_pcm_stream_lock_irqsave(sub, flags);
if (sub->runtime && snd_pcm_running(sub))
elapsed = gip_headset_copy_capture(stream, data, len);
snd_pcm_stream_unlock_irqrestore(sub, flags);
if (elapsed)
snd_pcm_period_elapsed(sub);
return 0;
}
static int gip_headset_probe(struct gip_client *client)
{
struct gip_headset *headset;
struct gip_info_element *fmts = client->audio_formats;
int err;
if (!fmts || !fmts->count)
return -ENODEV;
headset = devm_kzalloc(&client->dev, sizeof(*headset), GFP_KERNEL);
if (!headset)
return -ENOMEM;
headset->client = client;
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);
hrtimer_init(&headset->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
headset->timer.function = gip_headset_send_samples;
err = gip_enable_audio(client);
if (err)
return err;
err = gip_init_audio_in(client);
if (err) {
gip_disable_audio(client);
return err;
}
dev_set_drvdata(&client->dev, headset);
/* delay to prevent response from being dropped */
schedule_delayed_work(&headset->config_work, GIP_HS_CONFIG_DELAY);
return 0;
}
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);
hrtimer_cancel(&headset->timer);
gip_disable_audio(client);
if (headset->card) {
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 = {
.audio_ready = gip_headset_op_audio_ready,
.audio_volume = gip_headset_op_audio_volume,
.audio_samples = gip_headset_op_audio_samples,
},
.probe = gip_headset_probe,
.remove = gip_headset_remove,
};
module_gip_driver(gip_headset_driver);
MODULE_ALIAS("gip:Windows.Xbox.Input.Headset");
MODULE_AUTHOR("Severin von Wnuck <severinvonw@outlook.de>");
MODULE_DESCRIPTION("xone GIP headset driver");
MODULE_VERSION("#VERSION#");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,986 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/bitfield.h>
#include <linux/usb.h>
#include <linux/sysfs.h>
#include <linux/ieee80211.h>
#include <net/cfg80211.h>
#include "mt76.h"
#include "../bus/bus.h"
#define XONE_DONGLE_NUM_IN_URBS 12
#define XONE_DONGLE_NUM_OUT_URBS 12
#define XONE_DONGLE_LEN_CMD_PKT 0x0654
#define XONE_DONGLE_LEN_WLAN_PKT 0x8400
#define XONE_DONGLE_MAX_CLIENTS 16
#define XONE_DONGLE_PWR_OFF_TIMEOUT msecs_to_jiffies(5000)
enum xone_dongle_queue {
XONE_DONGLE_QUEUE_DATA = 0x00,
XONE_DONGLE_QUEUE_AUDIO = 0x02,
};
struct xone_dongle_skb_cb {
struct xone_dongle *dongle;
struct urb *urb;
};
struct xone_dongle_client {
struct xone_dongle *dongle;
u8 wcid;
u8 address[ETH_ALEN];
struct gip_adapter *adapter;
};
struct xone_dongle_event {
enum xone_dongle_event_type {
XONE_DONGLE_EVT_ADD_CLIENT,
XONE_DONGLE_EVT_REMOVE_CLIENT,
XONE_DONGLE_EVT_PAIR_CLIENT,
XONE_DONGLE_EVT_TOGGLE_PAIRING,
} type;
struct xone_dongle *dongle;
u8 address[ETH_ALEN];
u8 wcid;
struct work_struct work;
};
struct xone_dongle {
struct xone_mt76 mt;
struct usb_anchor urbs_in_idle;
struct usb_anchor urbs_in_busy;
struct usb_anchor urbs_out_idle;
struct usb_anchor urbs_out_busy;
/* serializes pairing changes */
struct mutex pairing_lock;
bool pairing;
/* serializes access to clients array */
spinlock_t clients_lock;
struct xone_dongle_client *clients[XONE_DONGLE_MAX_CLIENTS];
atomic_t client_count;
wait_queue_head_t disconnect_wait;
struct workqueue_struct *event_wq;
};
static void xone_dongle_prep_packet(struct xone_dongle_client *client,
struct sk_buff *skb,
enum xone_dongle_queue queue)
{
struct ieee80211_qos_hdr hdr = {};
struct mt76_txwi txwi = {};
u8 data[] = {
0x00, 0x00, queue, client->wcid - 1, 0x00, 0x00, 0x00, 0x00,
};
/* frame is sent from AP (DS) */
/* duration is the time required to transmit (in μs) */
hdr.frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA |
IEEE80211_STYPE_QOS_DATA |
IEEE80211_FCTL_FROMDS);
hdr.duration_id = cpu_to_le16(144);
memcpy(hdr.addr1, client->address, ETH_ALEN);
memcpy(hdr.addr2, client->dongle->mt.address, ETH_ALEN);
memcpy(hdr.addr3, client->dongle->mt.address, ETH_ALEN);
/* 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.len_ctl = cpu_to_le16(sizeof(hdr) + skb->len);
memset(skb_push(skb, 2), 0, 2);
memcpy(skb_push(skb, sizeof(hdr)), &hdr, sizeof(hdr));
memcpy(skb_push(skb, sizeof(txwi)), &txwi, sizeof(txwi));
memcpy(skb_push(skb, sizeof(data)), data, sizeof(data));
xone_mt76_prep_command(skb, 0);
}
static int xone_dongle_get_buffer(struct gip_adapter *adap,
struct gip_adapter_buffer *buf)
{
struct xone_dongle_client *client = dev_get_drvdata(&adap->dev);
struct xone_dongle_skb_cb *cb;
struct urb *urb;
struct sk_buff *skb;
urb = usb_get_from_anchor(&client->dongle->urbs_out_idle);
if (!urb)
return -ENOSPC;
skb = xone_mt76_alloc_message(XONE_DONGLE_LEN_CMD_PKT, GFP_ATOMIC);
if (!skb)
return -ENOMEM;
/* command header + WCID data + TXWI + QoS header + padding */
/* see xone_dongle_prep_packet and xone_mt76_prep_message */
skb_reserve(skb, MT_CMD_HDR_LEN + 8 + sizeof(struct mt76_txwi) +
sizeof(struct ieee80211_qos_hdr) + 2 + MT_CMD_HDR_LEN);
cb = (struct xone_dongle_skb_cb *)skb->cb;
cb->dongle = client->dongle;
cb->urb = urb;
buf->context = skb;
buf->data = skb->data;
buf->length = skb->len;
return 0;
}
static int xone_dongle_submit_buffer(struct gip_adapter *adap,
struct gip_adapter_buffer *buf)
{
struct xone_dongle_client *client = dev_get_drvdata(&adap->dev);
struct xone_dongle_skb_cb *cb;
struct sk_buff *skb = buf->context;
int err;
skb_put(skb, buf->length);
if (buf->type == GIP_BUF_DATA)
xone_dongle_prep_packet(client, skb, XONE_DONGLE_QUEUE_DATA);
else if (buf->type == GIP_BUF_AUDIO)
xone_dongle_prep_packet(client, skb, XONE_DONGLE_QUEUE_AUDIO);
else
return -EINVAL;
cb = (struct xone_dongle_skb_cb *)skb->cb;
cb->urb->context = skb;
cb->urb->transfer_buffer = skb->data;
cb->urb->transfer_buffer_length = skb->len;
usb_anchor_urb(cb->urb, &client->dongle->urbs_out_busy);
err = usb_submit_urb(cb->urb, GFP_ATOMIC);
if (err) {
usb_unanchor_urb(cb->urb);
usb_anchor_urb(cb->urb, &client->dongle->urbs_out_idle);
dev_kfree_skb_any(skb);
}
usb_free_urb(cb->urb);
return err;
}
static struct gip_adapter_ops xone_dongle_adapter_ops = {
.get_buffer = xone_dongle_get_buffer,
.submit_buffer = xone_dongle_submit_buffer,
};
static int xone_dongle_toggle_pairing(struct xone_dongle *dongle, bool enable)
{
int err = 0;
mutex_lock(&dongle->pairing_lock);
/* pairing is already enabled */
if (dongle->pairing && enable)
goto err_unlock;
err = xone_mt76_set_pairing(&dongle->mt, enable);
if (err)
goto err_unlock;
err = xone_mt76_set_led_mode(&dongle->mt, enable ? XONE_MT_LED_BLINK :
XONE_MT_LED_OFF);
if (err)
goto err_unlock;
dev_dbg(dongle->mt.dev, "%s: enabled=%d\n", __func__, enable);
dongle->pairing = enable;
err_unlock:
mutex_unlock(&dongle->pairing_lock);
return err;
}
static ssize_t xone_dongle_pairing_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
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;
int err;
err = kstrtobool(buf, &enable);
if (err)
return err;
err = xone_dongle_toggle_pairing(dongle, enable);
if (err)
return err;
return count;
}
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)
{
struct xone_dongle_client *client;
int i, err;
/* find free WCID */
for (i = 0; i < XONE_DONGLE_MAX_CLIENTS; i++)
if (!dongle->clients[i])
break;
if (i == XONE_DONGLE_MAX_CLIENTS)
return ERR_PTR(-ENOSPC);
client = kzalloc(sizeof(*client), GFP_KERNEL);
if (!client)
return ERR_PTR(-ENOMEM);
client->dongle = dongle;
client->wcid = i + 1;
memcpy(client->address, addr, ETH_ALEN);
client->adapter = gip_create_adapter(dongle->mt.dev,
&xone_dongle_adapter_ops, 1);
if (IS_ERR(client->adapter)) {
err = PTR_ERR(client->adapter);
kfree(client);
return ERR_PTR(err);
}
dev_set_drvdata(&client->adapter->dev, client);
return client;
}
static int xone_dongle_add_client(struct xone_dongle *dongle, u8 *addr)
{
struct xone_dongle_client *client;
int err;
unsigned long flags;
client = xone_dongle_create_client(dongle, addr);
if (IS_ERR(client))
return PTR_ERR(client);
err = xone_mt76_associate_client(&dongle->mt, client->wcid, addr);
if (err)
goto err_free_client;
err = xone_mt76_set_led_mode(&dongle->mt, XONE_MT_LED_ON);
if (err)
goto err_free_client;
dev_dbg(dongle->mt.dev, "%s: wcid=%d, address=%pM\n",
__func__, client->wcid, addr);
spin_lock_irqsave(&dongle->clients_lock, flags);
dongle->clients[client->wcid - 1] = client;
spin_unlock_irqrestore(&dongle->clients_lock, flags);
atomic_inc(&dongle->client_count);
return 0;
err_free_client:
gip_destroy_adapter(client->adapter);
kfree(client);
return err;
}
static int xone_dongle_remove_client(struct xone_dongle *dongle, u8 wcid)
{
struct xone_dongle_client *client;
int err;
unsigned long flags;
client = dongle->clients[wcid - 1];
if (!client)
return 0;
dev_dbg(dongle->mt.dev, "%s: wcid=%d, address=%pM\n",
__func__, wcid, client->address);
spin_lock_irqsave(&dongle->clients_lock, flags);
dongle->clients[wcid - 1] = NULL;
spin_unlock_irqrestore(&dongle->clients_lock, flags);
gip_destroy_adapter(client->adapter);
kfree(client);
err = xone_mt76_remove_client(&dongle->mt, wcid);
if (err)
dev_err(dongle->mt.dev, "%s: remove failed: %d\n",
__func__, err);
/* turn off LED if all clients have disconnected */
if (atomic_read(&dongle->client_count) == 1)
err = xone_mt76_set_led_mode(&dongle->mt, XONE_MT_LED_OFF);
atomic_dec(&dongle->client_count);
wake_up(&dongle->disconnect_wait);
return err;
}
static int xone_dongle_pair_client(struct xone_dongle *dongle, u8 *addr)
{
int err;
dev_dbg(dongle->mt.dev, "%s: address=%pM\n", __func__, addr);
err = xone_mt76_pair_client(&dongle->mt, addr);
if (err)
return err;
return xone_dongle_toggle_pairing(dongle, false);
}
static void xone_dongle_handle_event(struct work_struct *work)
{
struct xone_dongle_event *evt = container_of(work, typeof(*evt), work);
int err;
switch (evt->type) {
case XONE_DONGLE_EVT_ADD_CLIENT:
err = xone_dongle_add_client(evt->dongle, evt->address);
break;
case XONE_DONGLE_EVT_REMOVE_CLIENT:
err = xone_dongle_remove_client(evt->dongle, evt->wcid);
break;
case XONE_DONGLE_EVT_PAIR_CLIENT:
err = xone_dongle_pair_client(evt->dongle, evt->address);
break;
case XONE_DONGLE_EVT_TOGGLE_PAIRING:
err = xone_dongle_toggle_pairing(evt->dongle, true);
break;
}
if (err)
dev_err(evt->dongle->mt.dev, "%s: handle event failed: %d\n",
__func__, err);
kfree(evt);
}
static struct xone_dongle_event *
xone_dongle_alloc_event(struct xone_dongle *dongle,
enum xone_dongle_event_type type)
{
struct xone_dongle_event *evt;
evt = kzalloc(sizeof(*evt), GFP_ATOMIC);
if (!evt)
return NULL;
evt->type = type;
evt->dongle = dongle;
INIT_WORK(&evt->work, xone_dongle_handle_event);
return evt;
}
static int xone_dongle_handle_qos_data(struct xone_dongle *dongle,
struct sk_buff *skb, u8 wcid)
{
struct xone_dongle_client *client;
int err = 0;
unsigned long flags;
if (!wcid || wcid > XONE_DONGLE_MAX_CLIENTS)
return 0;
spin_lock_irqsave(&dongle->clients_lock, flags);
client = dongle->clients[wcid - 1];
if (client)
err = gip_process_buffer(client->adapter, skb->data, skb->len);
spin_unlock_irqrestore(&dongle->clients_lock, flags);
return err;
}
static int xone_dongle_handle_association(struct xone_dongle *dongle, u8 *addr)
{
struct xone_dongle_event *evt;
evt = xone_dongle_alloc_event(dongle, XONE_DONGLE_EVT_ADD_CLIENT);
if (!evt)
return -ENOMEM;
memcpy(evt->address, addr, ETH_ALEN);
queue_work(dongle->event_wq, &evt->work);
return 0;
}
static int xone_dongle_handle_disassociation(struct xone_dongle *dongle,
u8 wcid)
{
struct xone_dongle_event *evt;
if (!wcid || wcid > XONE_DONGLE_MAX_CLIENTS)
return 0;
evt = xone_dongle_alloc_event(dongle, XONE_DONGLE_EVT_REMOVE_CLIENT);
if (!evt)
return -ENOMEM;
evt->wcid = wcid;
queue_work(dongle->event_wq, &evt->work);
return 0;
}
static int xone_dongle_handle_reserved(struct xone_dongle *dongle,
struct sk_buff *skb, u8 *addr)
{
struct xone_dongle_event *evt;
if (skb->len < 2)
return -EINVAL;
if (skb->data[1] != 0x01)
return 0;
evt = xone_dongle_alloc_event(dongle, XONE_DONGLE_EVT_PAIR_CLIENT);
if (!evt)
return -ENOMEM;
memcpy(evt->address, addr, ETH_ALEN);
queue_work(dongle->event_wq, &evt->work);
return 0;
}
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);
if (!evt)
return -ENOMEM;
queue_work(dongle->event_wq, &evt->work);
return 0;
}
static int xone_dongle_handle_loss(struct xone_dongle *dongle,
struct sk_buff *skb)
{
u8 wcid;
if (skb->len < sizeof(wcid))
return -EINVAL;
wcid = skb->data[0];
if (!wcid || wcid > XONE_DONGLE_MAX_CLIENTS)
return 0;
dev_dbg(dongle->mt.dev, "%s: wcid=%d\n", __func__, wcid);
return xone_dongle_handle_disassociation(dongle, wcid);
}
static int xone_dongle_process_frame(struct xone_dongle *dongle,
struct sk_buff *skb,
unsigned int hdr_len, u8 wcid)
{
struct ieee80211_hdr_3addr *hdr =
(struct ieee80211_hdr_3addr *)skb->data;
u16 type;
/* ignore invalid frames */
if (skb->len < hdr_len || hdr_len < sizeof(*hdr))
return 0;
skb_pull(skb, hdr_len);
type = le16_to_cpu(hdr->frame_control);
switch (type & (IEEE80211_FCTL_FTYPE | IEEE80211_FCTL_STYPE)) {
case IEEE80211_FTYPE_DATA | IEEE80211_STYPE_QOS_DATA:
return xone_dongle_handle_qos_data(dongle, skb, wcid);
case IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_ASSOC_REQ:
return xone_dongle_handle_association(dongle, hdr->addr2);
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 0;
}
static int xone_dongle_process_wlan(struct xone_dongle *dongle,
struct sk_buff *skb)
{
struct mt76_rxwi *rxwi = (struct mt76_rxwi *)skb->data;
unsigned int hdr_len;
u32 ctl;
if (skb->len < sizeof(*rxwi))
return -EINVAL;
skb_pull(skb, sizeof(*rxwi));
hdr_len = ieee80211_get_hdrlen_from_skb(skb);
/* 2 bytes of padding after 802.11 header */
if (rxwi->rxinfo & cpu_to_le32(MT_RXINFO_L2PAD)) {
if (skb->len < hdr_len + 2)
return -EINVAL;
memmove(skb->data + 2, skb->data, hdr_len);
skb_pull(skb, 2);
}
ctl = le32_to_cpu(rxwi->ctl);
skb_trim(skb, FIELD_GET(MT_RXWI_CTL_MPDU_LEN, ctl));
return xone_dongle_process_frame(dongle, skb, hdr_len,
FIELD_GET(MT_RXWI_CTL_WCID, ctl));
}
static int xone_dongle_process_message(struct xone_dongle *dongle,
struct sk_buff *skb)
{
enum mt76_dma_msg_port port;
u32 info;
/* command header + trailer */
if (skb->len < MT_CMD_HDR_LEN * 2)
return -EINVAL;
info = get_unaligned_le32(skb->data);
port = FIELD_GET(MT_RX_FCE_INFO_D_PORT, info);
/* ignore command reponses */
if (FIELD_GET(MT_RX_FCE_INFO_CMD_SEQ, info) == 0x01)
return 0;
/* remove header + trailer */
skb_pull(skb, MT_CMD_HDR_LEN);
skb_trim(skb, skb->len - MT_CMD_HDR_LEN);
if (port == MT_WLAN_PORT)
return xone_dongle_process_wlan(dongle, skb);
if (port != MT_CPU_RX_PORT)
return 0;
switch (FIELD_GET(MT_RX_FCE_INFO_EVT_TYPE, info)) {
case XONE_MT_EVT_BUTTON:
return xone_dongle_handle_button(dongle);
case XONE_MT_EVT_PACKET_RX:
return xone_dongle_process_wlan(dongle, skb);
case XONE_MT_EVT_CLIENT_LOST:
return xone_dongle_handle_loss(dongle, skb);
}
return 0;
}
static int xone_dongle_process_buffer(struct xone_dongle *dongle,
void *data, int len)
{
struct sk_buff *skb;
int err;
if (!len)
return 0;
skb = dev_alloc_skb(len);
if (!skb)
return -ENOMEM;
skb_put_data(skb, data, len);
err = xone_dongle_process_message(dongle, skb);
if (err) {
dev_err(dongle->mt.dev, "%s: process failed: %d\n",
__func__, err);
print_hex_dump_debug("xone-dongle packet: ", DUMP_PREFIX_NONE,
16, 1, data, len, false);
}
dev_kfree_skb(skb);
return err;
}
static void xone_dongle_complete_in(struct urb *urb)
{
struct xone_dongle *dongle = urb->context;
int err;
switch (urb->status) {
case 0:
break;
case -ENOENT:
case -ECONNRESET:
case -ESHUTDOWN:
usb_anchor_urb(urb, &dongle->urbs_in_idle);
return;
default:
goto resubmit;
}
err = xone_dongle_process_buffer(dongle, urb->transfer_buffer,
urb->actual_length);
if (err)
dev_err(dongle->mt.dev, "%s: process failed: %d\n",
__func__, err);
resubmit:
/* can fail during USB device removal */
err = usb_submit_urb(urb, GFP_ATOMIC);
if (err) {
dev_dbg(dongle->mt.dev, "%s: submit failed: %d\n",
__func__, err);
usb_anchor_urb(urb, &dongle->urbs_in_idle);
} else {
usb_anchor_urb(urb, &dongle->urbs_in_busy);
}
}
static void xone_dongle_complete_out(struct urb *urb)
{
struct sk_buff *skb = urb->context;
struct xone_dongle_skb_cb *cb = (struct xone_dongle_skb_cb *)skb->cb;
usb_anchor_urb(urb, &cb->dongle->urbs_out_idle);
dev_consume_skb_any(skb);
}
static int xone_dongle_init_urbs_in(struct xone_dongle *dongle,
int ep, int buf_len)
{
struct xone_mt76 *mt = &dongle->mt;
struct urb *urb;
void *buf;
int i, err;
for (i = 0; i < XONE_DONGLE_NUM_IN_URBS; i++) {
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb)
return -ENOMEM;
usb_anchor_urb(urb, &dongle->urbs_in_busy);
usb_free_urb(urb);
buf = usb_alloc_coherent(mt->udev, buf_len,
GFP_KERNEL, &urb->transfer_dma);
if (!buf)
return -ENOMEM;
usb_fill_bulk_urb(urb, mt->udev,
usb_rcvbulkpipe(mt->udev, ep), buf, buf_len,
xone_dongle_complete_in, dongle);
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
err = usb_submit_urb(urb, GFP_KERNEL);
if (err)
return err;
}
return 0;
}
static int xone_dongle_init_urbs_out(struct xone_dongle *dongle)
{
struct xone_mt76 *mt = &dongle->mt;
struct urb *urb;
int i;
for (i = 0; i < XONE_DONGLE_NUM_OUT_URBS; i++) {
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb)
return -ENOMEM;
usb_fill_bulk_urb(urb, mt->udev,
usb_sndbulkpipe(mt->udev, XONE_MT_EP_OUT),
NULL, 0, xone_dongle_complete_out, NULL);
usb_anchor_urb(urb, &dongle->urbs_out_idle);
usb_free_urb(urb);
}
return 0;
}
static int xone_dongle_init(struct xone_dongle *dongle)
{
struct xone_mt76 *mt = &dongle->mt;
int err;
init_usb_anchor(&dongle->urbs_out_idle);
init_usb_anchor(&dongle->urbs_out_busy);
init_usb_anchor(&dongle->urbs_in_idle);
init_usb_anchor(&dongle->urbs_in_busy);
err = xone_dongle_init_urbs_out(dongle);
if (err)
return err;
err = xone_dongle_init_urbs_in(dongle, XONE_MT_EP_IN_CMD,
XONE_DONGLE_LEN_CMD_PKT);
if (err)
return err;
err = xone_dongle_init_urbs_in(dongle, XONE_MT_EP_IN_WLAN,
XONE_DONGLE_LEN_WLAN_PKT);
if (err)
return err;
err = xone_mt76_load_firmware(mt, "xow_dongle.bin");
if (err) {
dev_err(mt->dev, "%s: load firmware failed: %d\n",
__func__, err);
return err;
}
err = xone_mt76_init_radio(mt);
if (err)
dev_err(mt->dev, "%s: init radio failed: %d\n", __func__, err);
return err;
}
static int xone_dongle_power_off_clients(struct xone_dongle *dongle)
{
struct xone_dongle_client *client;
int i;
int err = 0;
unsigned long flags;
spin_lock_irqsave(&dongle->clients_lock, flags);
for (i = 0; i < XONE_DONGLE_MAX_CLIENTS; i++) {
client = dongle->clients[i];
if (!client)
continue;
err = gip_power_off_adapter(client->adapter);
if (err)
break;
}
spin_unlock_irqrestore(&dongle->clients_lock, flags);
if (err)
return err;
/* can time out if new client connects */
if (!wait_event_timeout(dongle->disconnect_wait,
!atomic_read(&dongle->client_count),
XONE_DONGLE_PWR_OFF_TIMEOUT))
return -ETIMEDOUT;
return 0;
}
static void xone_dongle_destroy(struct xone_dongle *dongle)
{
struct xone_dongle_client *client;
struct urb *urb;
int i;
usb_kill_anchored_urbs(&dongle->urbs_in_busy);
destroy_workqueue(dongle->event_wq);
for (i = 0; i < XONE_DONGLE_MAX_CLIENTS; i++) {
client = dongle->clients[i];
if (!client)
continue;
gip_destroy_adapter(client->adapter);
kfree(client);
dongle->clients[i] = NULL;
}
usb_kill_anchored_urbs(&dongle->urbs_out_busy);
while ((urb = usb_get_from_anchor(&dongle->urbs_out_idle)))
usb_free_urb(urb);
while ((urb = usb_get_from_anchor(&dongle->urbs_in_idle))) {
usb_free_coherent(urb->dev, urb->transfer_buffer_length,
urb->transfer_buffer, urb->transfer_dma);
usb_free_urb(urb);
}
mutex_destroy(&dongle->pairing_lock);
}
static int xone_dongle_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct xone_dongle *dongle;
int err;
dongle = devm_kzalloc(&intf->dev, sizeof(*dongle), GFP_KERNEL);
if (!dongle)
return -ENOMEM;
dongle->mt.dev = &intf->dev;
dongle->mt.udev = interface_to_usbdev(intf);
usb_reset_device(dongle->mt.udev);
/* enable USB remote wakeup feature */
device_wakeup_enable(&dongle->mt.udev->dev);
dongle->event_wq = alloc_ordered_workqueue("xone_dongle", 0);
if (!dongle->event_wq)
return -ENOMEM;
mutex_init(&dongle->pairing_lock);
spin_lock_init(&dongle->clients_lock);
init_waitqueue_head(&dongle->disconnect_wait);
err = xone_dongle_init(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)
xone_dongle_destroy(dongle);
return err;
}
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)
dev_dbg(dongle->mt.dev, "%s: power off failed: %d\n",
__func__, err);
xone_dongle_destroy(dongle);
usb_set_intfdata(intf, NULL);
}
static int xone_dongle_suspend(struct usb_interface *intf, pm_message_t message)
{
struct xone_dongle *dongle = usb_get_intfdata(intf);
int err;
err = xone_dongle_power_off_clients(dongle);
if (err)
dev_err(dongle->mt.dev, "%s: power off failed: %d\n",
__func__, err);
usb_kill_anchored_urbs(&dongle->urbs_in_busy);
usb_kill_anchored_urbs(&dongle->urbs_out_busy);
flush_workqueue(dongle->event_wq);
return xone_mt76_suspend_radio(&dongle->mt);
}
static int xone_dongle_resume(struct usb_interface *intf)
{
struct xone_dongle *dongle = usb_get_intfdata(intf);
struct urb *urb;
int err;
while ((urb = usb_get_from_anchor(&dongle->urbs_in_idle))) {
usb_anchor_urb(urb, &dongle->urbs_in_busy);
usb_free_urb(urb);
err = usb_submit_urb(urb, GFP_KERNEL);
if (err)
return err;
}
return xone_mt76_resume_radio(&dongle->mt);
}
static void xone_dongle_shutdown(struct device *dev)
{
struct usb_interface *intf = to_usb_interface(dev);
struct xone_dongle *dongle = usb_get_intfdata(intf);
int err;
err = xone_dongle_power_off_clients(dongle);
if (err)
dev_err(dongle->mt.dev, "%s: power off failed: %d\n",
__func__, err);
}
static const struct usb_device_id xone_dongle_id_table[] = {
{ USB_DEVICE(0x045e, 0x02e6) }, /* old dongle */
{ USB_DEVICE(0x045e, 0x02fe) }, /* new dongle */
{ USB_DEVICE(0x045e, 0x02f9) }, /* built-in dongle (ASUS, Lenovo) */
{ USB_DEVICE(0x045e, 0x091e) }, /* built-in dongle (Surface Book 2) */
{ },
};
static struct usb_driver xone_dongle_driver = {
.name = "xone-dongle",
.probe = xone_dongle_probe,
.disconnect = xone_dongle_disconnect,
.suspend = xone_dongle_suspend,
.resume = xone_dongle_resume,
.id_table = xone_dongle_id_table,
.drvwrap.driver.shutdown = xone_dongle_shutdown,
.soft_unbind = 1,
.disable_hub_initiated_lpm = 1,
};
module_usb_driver(xone_dongle_driver);
MODULE_DEVICE_TABLE(usb, xone_dongle_id_table);
MODULE_AUTHOR("Severin von Wnuck <severinvonw@outlook.de>");
MODULE_DESCRIPTION("xone dongle driver");
MODULE_VERSION("#VERSION#");
MODULE_LICENSE("GPL");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,65 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
*/
#pragma once
#include "mt76_defs.h"
#define XONE_MT_EP_IN_CMD 0x05
#define XONE_MT_EP_IN_WLAN 0x04
#define XONE_MT_EP_OUT 0x04
#define XONE_MT_NUM_CHANNELS 12
/* 802.11 frame subtype: reserved */
#define XONE_MT_WLAN_RESERVED 0x70
enum xone_mt76_led_mode {
XONE_MT_LED_BLINK = 0x00,
XONE_MT_LED_ON = 0x01,
XONE_MT_LED_OFF = 0x02,
};
enum xone_mt76_event {
XONE_MT_EVT_BUTTON = 0x04,
XONE_MT_EVT_CHANNELS = 0x0a,
XONE_MT_EVT_PACKET_RX = 0x0c,
XONE_MT_EVT_COREDUMP = 0x0d,
XONE_MT_EVT_CLIENT_LOST = 0x0e,
};
struct xone_mt76_channel {
u8 index;
u8 band;
enum mt76_phy_bandwidth bandwidth;
enum mt76_cal_channel_group group;
bool scan;
u8 power;
};
struct xone_mt76 {
struct device *dev;
struct usb_device *udev;
__le32 control_data;
u8 address[ETH_ALEN];
struct xone_mt76_channel channels[XONE_MT_NUM_CHANNELS];
struct xone_mt76_channel *channel;
};
struct sk_buff *xone_mt76_alloc_message(int len, gfp_t gfp);
void xone_mt76_prep_command(struct sk_buff *skb, enum mt76_mcu_cmd cmd);
int xone_mt76_set_led_mode(struct xone_mt76 *mt, enum xone_mt76_led_mode mode);
int xone_mt76_load_firmware(struct xone_mt76 *mt, const char *name);
int xone_mt76_init_radio(struct xone_mt76 *mt);
int xone_mt76_suspend_radio(struct xone_mt76 *mt);
int xone_mt76_resume_radio(struct xone_mt76 *mt);
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_remove_client(struct xone_mt76 *mt, u8 wcid);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,553 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2021 Severin von Wnuck <severinvonw@outlook.de>
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include "../bus/bus.h"
#define XONE_WIRED_INTF_DATA 0
#define XONE_WIRED_INTF_AUDIO 1
#define XONE_WIRED_NUM_DATA_URBS 8
#define XONE_WIRED_NUM_AUDIO_URBS 12
#define XONE_WIRED_NUM_AUDIO_PKTS 8
#define XONE_WIRED_LEN_DATA_PKT 64
#define XONE_WIRED_VENDOR(vendor) \
.match_flags = USB_DEVICE_ID_MATCH_VENDOR | \
USB_DEVICE_ID_MATCH_INT_INFO | \
USB_DEVICE_ID_MATCH_INT_NUMBER, \
.idVendor = vendor, \
.bInterfaceClass = USB_CLASS_VENDOR_SPEC, \
.bInterfaceSubClass = 0x47, \
.bInterfaceProtocol = 0xd0, \
.bInterfaceNumber = XONE_WIRED_INTF_DATA,
struct xone_wired {
struct usb_device *udev;
struct xone_wired_port {
struct usb_interface *intf;
struct usb_endpoint_descriptor *ep_in;
struct usb_endpoint_descriptor *ep_out;
struct urb *urb_in;
struct usb_anchor urbs_out_idle;
struct usb_anchor urbs_out_busy;
int buffer_length_out;
} data_port, audio_port;
struct gip_adapter *adapter;
};
static void xone_wired_complete_data_in(struct urb *urb)
{
struct xone_wired *wired = urb->context;
struct device *dev = &wired->data_port.intf->dev;
int err;
switch (urb->status) {
case 0:
break;
case -ENOENT:
case -ECONNRESET:
case -ESHUTDOWN:
return;
default:
goto resubmit;
}
if (!urb->actual_length)
goto resubmit;
err = gip_process_buffer(wired->adapter, urb->transfer_buffer,
urb->actual_length);
if (err)
dev_err(dev, "%s: process failed: %d\n", __func__, err);
resubmit:
/* can fail during USB device removal */
err = usb_submit_urb(urb, GFP_ATOMIC);
if (err)
dev_dbg(dev, "%s: submit failed: %d\n", __func__, err);
}
static void xone_wired_complete_audio_in(struct urb *urb)
{
struct xone_wired *wired = urb->context;
struct device *dev = &wired->audio_port.intf->dev;
struct usb_iso_packet_descriptor *desc;
int i, err;
if (urb->status)
return;
for (i = 0; i < urb->number_of_packets; i++) {
desc = &urb->iso_frame_desc[i];
/* device reset after system sleep can cause xHCI errors */
if (desc->status == -EPROTO) {
dev_warn_once(dev, "%s: protocol error\n", __func__);
break;
}
if (!desc->actual_length)
continue;
err = gip_process_buffer(wired->adapter,
urb->transfer_buffer + desc->offset,
desc->actual_length);
if (err)
dev_err(dev, "%s: process failed: %d\n", __func__, err);
}
/* can fail during USB device removal */
err = usb_submit_urb(urb, GFP_ATOMIC);
if (err)
dev_dbg(dev, "%s: submit failed: %d\n", __func__, err);
}
static void xone_wired_complete_out(struct urb *urb)
{
struct xone_wired_port *port = urb->context;
usb_anchor_urb(urb, &port->urbs_out_idle);
}
static int xone_wired_init_data_in(struct xone_wired *wired)
{
struct xone_wired_port *port = &wired->data_port;
struct urb *urb;
void *buf;
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb)
return -ENOMEM;
port->urb_in = urb;
buf = usb_alloc_coherent(wired->udev, XONE_WIRED_LEN_DATA_PKT,
GFP_KERNEL, &urb->transfer_dma);
if (!buf)
return -ENOMEM;
usb_fill_int_urb(urb, wired->udev,
usb_rcvintpipe(wired->udev,
port->ep_in->bEndpointAddress),
buf, XONE_WIRED_LEN_DATA_PKT,
xone_wired_complete_data_in, wired,
port->ep_in->bInterval);
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
return usb_submit_urb(urb, GFP_KERNEL);
}
static int xone_wired_init_data_out(struct xone_wired *wired)
{
struct xone_wired_port *port = &wired->data_port;
struct urb *urb;
void *buf;
int i;
port->buffer_length_out = XONE_WIRED_LEN_DATA_PKT;
for (i = 0; i < XONE_WIRED_NUM_DATA_URBS; i++) {
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb)
return -ENOMEM;
usb_anchor_urb(urb, &port->urbs_out_idle);
usb_free_urb(urb);
buf = usb_alloc_coherent(wired->udev, XONE_WIRED_LEN_DATA_PKT,
GFP_KERNEL, &urb->transfer_dma);
if (!buf)
return -ENOMEM;
usb_fill_int_urb(urb, wired->udev,
usb_sndintpipe(wired->udev,
port->ep_out->bEndpointAddress),
buf, XONE_WIRED_LEN_DATA_PKT,
xone_wired_complete_out, port,
port->ep_out->bInterval);
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
}
return 0;
}
static void xone_wired_free_urbs(struct xone_wired_port *port)
{
struct urb *urb = port->urb_in;
if (urb) {
usb_free_coherent(urb->dev, urb->transfer_buffer_length,
urb->transfer_buffer, urb->transfer_dma);
usb_free_urb(urb);
port->urb_in = NULL;
}
while ((urb = usb_get_from_anchor(&port->urbs_out_idle))) {
usb_free_coherent(urb->dev, port->buffer_length_out,
urb->transfer_buffer, urb->transfer_dma);
usb_free_urb(urb);
}
}
static int xone_wired_get_buffer(struct gip_adapter *adap,
struct gip_adapter_buffer *buf)
{
struct xone_wired *wired = dev_get_drvdata(&adap->dev);
struct xone_wired_port *port;
struct urb *urb;
if (buf->type == GIP_BUF_DATA)
port = &wired->data_port;
else if (buf->type == GIP_BUF_AUDIO)
port = &wired->audio_port;
else
return -EINVAL;
urb = usb_get_from_anchor(&port->urbs_out_idle);
if (!urb)
return -ENOSPC;
buf->context = urb;
buf->data = urb->transfer_buffer;
buf->length = port->buffer_length_out;
return 0;
}
static int xone_wired_submit_buffer(struct gip_adapter *adap,
struct gip_adapter_buffer *buf)
{
struct xone_wired *wired = dev_get_drvdata(&adap->dev);
struct xone_wired_port *port;
struct urb *urb = buf->context;
int err;
if (buf->type == GIP_BUF_DATA)
port = &wired->data_port;
else if (buf->type == GIP_BUF_AUDIO)
port = &wired->audio_port;
else
return -EINVAL;
urb->transfer_buffer_length = buf->length;
usb_anchor_urb(urb, &port->urbs_out_busy);
err = usb_submit_urb(urb, GFP_ATOMIC);
if (err) {
usb_unanchor_urb(urb);
usb_anchor_urb(urb, &port->urbs_out_idle);
}
usb_free_urb(urb);
return err;
}
static int xone_wired_enable_audio(struct gip_adapter *adap)
{
struct xone_wired *wired = dev_get_drvdata(&adap->dev);
struct xone_wired_port *port = &wired->audio_port;
if (port->intf->cur_altsetting->desc.bAlternateSetting == 1)
return -EALREADY;
return usb_set_interface(wired->udev, XONE_WIRED_INTF_AUDIO, 1);
}
static int xone_wired_init_audio_in(struct gip_adapter *adap)
{
struct xone_wired *wired = dev_get_drvdata(&adap->dev);
struct xone_wired_port *port = &wired->audio_port;
struct urb *urb;
void *buf;
int len, i;
urb = usb_alloc_urb(XONE_WIRED_NUM_AUDIO_PKTS, GFP_KERNEL);
if (!urb)
return -ENOMEM;
port->urb_in = urb;
len = usb_endpoint_maxp(port->ep_in);
buf = usb_alloc_coherent(wired->udev, len * XONE_WIRED_NUM_AUDIO_PKTS,
GFP_KERNEL, &urb->transfer_dma);
if (!buf)
return -ENOMEM;
urb->dev = wired->udev;
urb->pipe = usb_rcvisocpipe(wired->udev, port->ep_in->bEndpointAddress);
urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
urb->transfer_buffer = buf;
urb->transfer_buffer_length = len * XONE_WIRED_NUM_AUDIO_PKTS;
urb->number_of_packets = XONE_WIRED_NUM_AUDIO_PKTS;
urb->interval = port->ep_in->bInterval;
urb->context = wired;
urb->complete = xone_wired_complete_audio_in;
for (i = 0; i < XONE_WIRED_NUM_AUDIO_PKTS; i++) {
urb->iso_frame_desc[i].offset = i * len;
urb->iso_frame_desc[i].length = len;
}
return usb_submit_urb(urb, GFP_KERNEL);
}
static int xone_wired_init_audio_out(struct gip_adapter *adap, int pkt_len)
{
struct xone_wired *wired = dev_get_drvdata(&adap->dev);
struct xone_wired_port *port = &wired->audio_port;
struct urb *urb;
void *buf;
int i, j;
port->buffer_length_out = pkt_len * XONE_WIRED_NUM_AUDIO_PKTS;
for (i = 0; i < XONE_WIRED_NUM_AUDIO_URBS; i++) {
urb = usb_alloc_urb(XONE_WIRED_NUM_AUDIO_PKTS, GFP_KERNEL);
if (!urb)
return -ENOMEM;
usb_anchor_urb(urb, &port->urbs_out_idle);
usb_free_urb(urb);
buf = usb_alloc_coherent(wired->udev, port->buffer_length_out,
GFP_KERNEL, &urb->transfer_dma);
if (!buf)
return -ENOMEM;
urb->dev = wired->udev;
urb->pipe = usb_sndisocpipe(wired->udev,
port->ep_out->bEndpointAddress);
urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
urb->transfer_buffer = buf;
urb->transfer_buffer_length = port->buffer_length_out;
urb->number_of_packets = XONE_WIRED_NUM_AUDIO_PKTS;
urb->interval = port->ep_out->bInterval;
urb->context = port;
urb->complete = xone_wired_complete_out;
for (j = 0; j < XONE_WIRED_NUM_AUDIO_PKTS; j++) {
urb->iso_frame_desc[j].offset = j * pkt_len;
urb->iso_frame_desc[j].length = pkt_len;
}
}
return 0;
}
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;
if (!port->intf->cur_altsetting->desc.bAlternateSetting)
return -EALREADY;
usb_kill_urb(port->urb_in);
usb_kill_anchored_urbs(&port->urbs_out_busy);
xone_wired_free_urbs(port);
return usb_set_interface(wired->udev, XONE_WIRED_INTF_AUDIO, 0);
}
static struct gip_adapter_ops xone_wired_adapter_ops = {
.get_buffer = xone_wired_get_buffer,
.submit_buffer = xone_wired_submit_buffer,
.enable_audio = xone_wired_enable_audio,
.init_audio_in = xone_wired_init_audio_in,
.init_audio_out = xone_wired_init_audio_out,
.disable_audio = xone_wired_disable_audio,
};
static struct usb_driver xone_wired_driver;
static int xone_wired_find_isoc_endpoints(struct usb_host_interface *alt,
struct usb_endpoint_descriptor **in,
struct usb_endpoint_descriptor **out)
{
struct usb_endpoint_descriptor *ep;
int i;
for (i = 0; i < alt->desc.bNumEndpoints; i++) {
ep = &alt->endpoint[i].desc;
if (usb_endpoint_is_isoc_in(ep))
*in = ep;
else if (usb_endpoint_is_isoc_out(ep))
*out = ep;
if (*in && *out)
return 0;
}
return -ENXIO;
}
static int xone_wired_init_data_port(struct xone_wired *wired,
struct usb_interface *intf)
{
struct xone_wired_port *port = &wired->data_port;
int err;
err = usb_find_common_endpoints(intf->cur_altsetting, NULL, NULL,
&port->ep_in, &port->ep_out);
if (err)
return err;
port->intf = intf;
init_usb_anchor(&port->urbs_out_idle);
init_usb_anchor(&port->urbs_out_busy);
return 0;
}
static int xone_wired_init_audio_port(struct xone_wired *wired)
{
struct xone_wired_port *port = &wired->audio_port;
struct usb_interface *intf;
struct usb_host_interface *alt;
int err;
intf = usb_ifnum_to_if(wired->udev, XONE_WIRED_INTF_AUDIO);
if (!intf)
return -ENODEV;
alt = usb_altnum_to_altsetting(intf, 1);
if (!alt)
return -ENODEV;
err = usb_driver_claim_interface(&xone_wired_driver, intf, NULL);
if (err)
return err;
/* disable the audio interface */
/* mandatory for certain third party devices */
err = usb_set_interface(wired->udev, XONE_WIRED_INTF_AUDIO, 0);
if (err)
return err;
err = xone_wired_find_isoc_endpoints(alt, &port->ep_in, &port->ep_out);
if (err)
return err;
port->intf = intf;
init_usb_anchor(&port->urbs_out_idle);
init_usb_anchor(&port->urbs_out_busy);
return 0;
}
static int xone_wired_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct xone_wired *wired;
int err;
wired = devm_kzalloc(&intf->dev, sizeof(*wired), GFP_KERNEL);
if (!wired)
return -ENOMEM;
wired->udev = interface_to_usbdev(intf);
/* newer devices require a reset after system sleep */
usb_reset_device(wired->udev);
/* enable USB remote wakeup feature */
device_wakeup_enable(&wired->udev->dev);
err = xone_wired_init_data_port(wired, intf);
if (err)
return err;
err = xone_wired_init_audio_port(wired);
if (err)
return err;
wired->adapter = gip_create_adapter(&intf->dev, &xone_wired_adapter_ops,
XONE_WIRED_NUM_AUDIO_PKTS);
if (IS_ERR(wired->adapter))
return PTR_ERR(wired->adapter);
dev_set_drvdata(&wired->adapter->dev, wired);
err = xone_wired_init_data_out(wired);
if (err)
goto err_free_urbs;
err = xone_wired_init_data_in(wired);
if (err)
goto err_free_urbs;
usb_set_intfdata(intf, wired);
return 0;
err_free_urbs:
xone_wired_free_urbs(&wired->data_port);
gip_destroy_adapter(wired->adapter);
return err;
}
static void xone_wired_disconnect(struct usb_interface *intf)
{
struct xone_wired *wired = usb_get_intfdata(intf);
/* ignore audio interface unbind */
if (!wired)
return;
usb_kill_urb(wired->data_port.urb_in);
usb_kill_urb(wired->audio_port.urb_in);
/* also disables the audio interface */
gip_destroy_adapter(wired->adapter);
usb_kill_anchored_urbs(&wired->data_port.urbs_out_busy);
xone_wired_free_urbs(&wired->data_port);
usb_set_intfdata(intf, NULL);
}
static const struct usb_device_id xone_wired_id_table[] = {
{ XONE_WIRED_VENDOR(0x045e) }, /* Microsoft */
{ XONE_WIRED_VENDOR(0x0738) }, /* Mad Catz */
{ XONE_WIRED_VENDOR(0x0e6f) }, /* PDP */
{ XONE_WIRED_VENDOR(0x0f0d) }, /* Hori */
{ XONE_WIRED_VENDOR(0x1532) }, /* Razer */
{ XONE_WIRED_VENDOR(0x24c6) }, /* PowerA */
{ XONE_WIRED_VENDOR(0x20d6) }, /* BDA */
{ XONE_WIRED_VENDOR(0x044f) }, /* Thrustmaster */
{ XONE_WIRED_VENDOR(0x10f5) }, /* Turtle Beach */
{ XONE_WIRED_VENDOR(0x2e24) }, /* Hyperkin */
{ XONE_WIRED_VENDOR(0x3285) }, /* Nacon */
{ XONE_WIRED_VENDOR(0x2dc8) }, /* 8BitDo */
{ XONE_WIRED_VENDOR(0x2e95) }, /* SCUF */
{ },
};
static struct usb_driver xone_wired_driver = {
.name = "xone-wired",
.probe = xone_wired_probe,
.disconnect = xone_wired_disconnect,
.id_table = xone_wired_id_table,
};
module_usb_driver(xone_wired_driver);
MODULE_DEVICE_TABLE(usb, xone_wired_id_table);
MODULE_AUTHOR("Severin von Wnuck <severinvonw@outlook.de>");
MODULE_DESCRIPTION("xone wired driver");
MODULE_VERSION("#VERSION#");
MODULE_LICENSE("GPL");