1
0
mirror of https://github.com/medusalix/xone.git synced 2024-11-25 05:26:13 +00:00
xone/bus/bus.c
medusalix 85f190fbe6
Implement chunked packet transmission
Larger packets were previously split into chunks and sent in one go,
despite requiring multiple acknowledgments.
Add a chunk buffer to send chunks after acknowledgments are received.
Fixes the authentication handshake for PowerA gamepads.
2024-04-24 15:28:53 +02:00

352 lines
7.8 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2021 Severin von Wnuck-Lipinski <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,
};
#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;
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_out);
kfree(client->chunk_buf_in);
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 = 0;
if (down_interruptible(&client->drv_lock))
return -EINTR;
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;
down(&client->drv_lock);
drv = client->drv;
if (drv) {
client->drv = NULL;
if (drv->remove)
drv->remove(client);
}
up(&client->drv_lock);
}
#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->clients_wq = alloc_ordered_workqueue("gip%d", 0, adap->id);
if (!adap->clients_wq) {
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->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->clients_wq);
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->clients_wq);
for (i = GIP_MAX_CLIENTS - 1; i >= 0; i--) {
client = adap->clients[i];
if (!client || !device_is_registered(&client->dev))
continue;
device_unregister(&client->dev);
}
ida_simple_remove(&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 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->id = id;
client->adapter = adap;
sema_init(&client->drv_lock, 1);
INIT_WORK(&client->work_register, gip_register_client);
INIT_WORK(&client->work_unregister, gip_unregister_client);
adap->clients[id] = client;
dev_dbg(&client->adapter->dev, "%s: initialized client %u\n",
__func__, id);
return client;
}
void gip_add_client(struct gip_client *client)
{
queue_work(client->adapter->clients_wq, &client->work_register);
}
void gip_remove_client(struct gip_client *client)
{
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->client_commands);
kfree(client->firmware_versions);
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->client_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-Lipinski <severinvonw@outlook.de>");
MODULE_DESCRIPTION("xone GIP driver");
MODULE_VERSION("#VERSION#");
MODULE_LICENSE("GPL");