// SPDX-License-Identifier: GPL-2.0-or-later
/*
* The Serio abstraction module
*
* Copyright (c) 1999-2004 Vojtech Pavlik
* Copyright (c) 2004 Dmitry Torokhov
* Copyright (c) 2003 Daniele Bellucci
*/
/*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/stddef.h>
#include <linux/module.h>
#include <linux/serio.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/mutex.h>
MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
MODULE_DESCRIPTION("Serio abstraction core");
MODULE_LICENSE("GPL");
/*
* serio_mutex protects entire serio subsystem and is taken every time
* serio port or driver registered or unregistered.
*/
static DEFINE_MUTEX(serio_mutex);
static LIST_HEAD(serio_list);
static void serio_add_port(struct serio *serio);
static int serio_reconnect_port(struct serio *serio);
static void serio_disconnect_port(struct serio *serio);
static void serio_reconnect_subtree(struct serio *serio);
static void serio_attach_driver(struct serio_driver *drv);
static int serio_connect_driver(struct serio *serio, struct serio_driver *drv)
{
int retval;
mutex_lock(&serio->drv_mutex);
retval = drv->connect(serio, drv);
mutex_unlock(&serio->drv_mutex);
return retval;
}
static int serio_reconnect_driver(struct serio *serio)
{
int retval = -1;
mutex_lock(&serio->drv_mutex);
if (serio->drv && serio->drv->reconnect)
retval = serio->drv->reconnect(serio);
mutex_unlock(&serio->drv_mutex);
return retval;
}
static void serio_disconnect_driver(struct serio *serio)
{
mutex_lock(&serio->drv_mutex);
if (serio->drv)
serio->drv->disconnect(serio);
mutex_unlock(&serio->drv_mutex);
}
static int serio_match_port(const struct serio_device_id *ids, struct serio *serio)
{
while (ids->type || ids->proto) {
if ((ids->type == SERIO_ANY || ids->type == serio->id.type) &&
(ids->proto == SERIO_ANY || ids->proto == serio->id.proto) &&
(ids->extra == SERIO_ANY || ids->extra == serio->id.extra) &&
(ids->id == SERIO_ANY || ids->id == serio->id.id))
return 1;
ids++;
}
return 0;
}
/*
* Basic serio -> driver core mappings
*/
static int serio_bind_driver(struct serio *serio, struct serio_driver *drv)
{
int error;
if (serio_match_port(drv->id_table, serio)) {
serio->dev.driver = &drv->driver;
if (serio_connect_driver(serio, drv)) {
serio->dev.driver = NULL;
return -ENODEV;
}
error = device_bind_driver(&serio->dev);
if (error) {
dev_warn(&serio->dev,
"device_bind_driver() failed for %s (%s) and %s, error: %d\n",
serio->phys, serio->name,
drv->description, error);
serio_disconnect_driver(serio);
serio->dev.driver = NULL;
return error;
}
}
return 0;
}
static void serio_find_driver(struct serio *serio)
{
int error;
error = device_attach(&serio->dev);
if (error < 0 && error != -EPROBE_DEFER)
dev_warn(&serio->dev,
"device_attach() failed for %s (%s), error: %d\n",
serio->phys, serio->name, error);
}
/*
* Serio event processing.
*/
enum serio_event_type {
SERIO_RESCAN_PORT,
SERIO_RECONNECT_PORT,
SERIO_RECONNECT_SUBTREE,
SERIO_REGISTER_PORT,
SERIO_ATTACH_DRIVER,
};
struct serio_event {