1
0
Fork 0
alistair23-linux/drivers/staging/greybus/protocol.c

202 lines
4.5 KiB
C

/*
* Greybus protocol handling
*
* Copyright 2014-2015 Google Inc.
* Copyright 2014-2015 Linaro Ltd.
*
* Released under the GPLv2 only.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include "greybus.h"
/* Global list of registered protocols */
static DEFINE_SPINLOCK(gb_protocols_lock);
static LIST_HEAD(gb_protocols);
/* Caller must hold gb_protocols_lock */
static struct gb_protocol *gb_protocol_find(u8 id, u8 major, u8 minor)
{
struct gb_protocol *protocol;
list_for_each_entry(protocol, &gb_protocols, links) {
if (protocol->id < id)
continue;
if (protocol->id > id)
break;
if (protocol->major > major)
continue;
if (protocol->major < major)
break;
if (protocol->minor > minor)
continue;
if (protocol->minor < minor)
break;
return protocol;
}
return NULL;
}
int __gb_protocol_register(struct gb_protocol *protocol, struct module *module)
{
struct gb_protocol *existing;
u8 id = protocol->id;
u8 major = protocol->major;
u8 minor = protocol->minor;
protocol->owner = module;
/*
* The protocols list is sorted first by protocol id (low to
* high), then by major version (high to low), and finally
* by minor version (high to low). Searching only by
* protocol id will produce the newest implemented version
* of the protocol.
*/
spin_lock_irq(&gb_protocols_lock);
list_for_each_entry(existing, &gb_protocols, links) {
if (existing->id < id)
continue;
if (existing->id > id)
break;
if (existing->major > major)
continue;
if (existing->major < major)
break;
if (existing->minor > minor)
continue;
if (existing->minor < minor)
break;
/* A matching protocol has already been registered */
spin_unlock_irq(&gb_protocols_lock);
return -EEXIST;
}
/*
* We need to insert the protocol here, before the existing one
* (or before the head if we searched the whole list)
*/
list_add_tail(&protocol->links, &existing->links);
spin_unlock_irq(&gb_protocols_lock);
pr_info("Registered %s protocol.\n", protocol->name);
return 0;
}
EXPORT_SYMBOL_GPL(__gb_protocol_register);
/*
* De-register a previously registered protocol.
*/
void gb_protocol_deregister(struct gb_protocol *protocol)
{
if (!protocol)
return;
spin_lock_irq(&gb_protocols_lock);
protocol = gb_protocol_find(protocol->id, protocol->major,
protocol->minor);
if (WARN_ON(!protocol || protocol->count)) {
spin_unlock_irq(&gb_protocols_lock);
return;
}
list_del(&protocol->links);
spin_unlock_irq(&gb_protocols_lock);
pr_info("Deregistered %s protocol.\n", protocol->name);
}
EXPORT_SYMBOL_GPL(gb_protocol_deregister);
/* Returns the requested protocol if available, or a null pointer */
struct gb_protocol *gb_protocol_get(u8 id, u8 major, u8 minor)
{
struct gb_protocol *protocol;
u8 protocol_count;
spin_lock_irq(&gb_protocols_lock);
protocol = gb_protocol_find(id, major, minor);
if (protocol) {
if (!try_module_get(protocol->owner)) {
protocol = NULL;
} else {
protocol_count = protocol->count;
if (protocol_count != U8_MAX)
protocol->count++;
}
}
spin_unlock_irq(&gb_protocols_lock);
if (protocol)
WARN_ON(protocol_count == U8_MAX);
return protocol;
}
EXPORT_SYMBOL_GPL(gb_protocol_get);
int gb_protocol_get_version(struct gb_connection *connection)
{
struct gb_protocol_version_request request;
struct gb_protocol_version_response response;
struct gb_protocol *protocol = connection->protocol;
int retval;
request.major = protocol->major;
request.minor = protocol->minor;
retval = gb_operation_sync(connection, GB_REQUEST_TYPE_PROTOCOL_VERSION,
&request, sizeof(request), &response,
sizeof(response));
if (retval)
return retval;
if (response.major > connection->protocol->major) {
dev_err(&connection->hd->dev,
"%s: unsupported major version (%u > %u)\n",
connection->name, response.major,
connection->protocol->major);
return -ENOTSUPP;
}
connection->module_major = response.major;
connection->module_minor = response.minor;
dev_dbg(&connection->hd->dev,
"%s: %s (0x%02x) v%u.%u\n", connection->name,
protocol->name, protocol->id, response.major, response.minor);
return 0;
}
EXPORT_SYMBOL_GPL(gb_protocol_get_version);
void gb_protocol_put(struct gb_protocol *protocol)
{
u8 id;
u8 major;
u8 minor;
id = protocol->id;
major = protocol->major;
minor = protocol->minor;
spin_lock_irq(&gb_protocols_lock);
protocol = gb_protocol_find(id, major, minor);
if (WARN_ON(!protocol || !protocol->count))
goto out;
protocol->count--;
module_put(protocol->owner);
out:
spin_unlock_irq(&gb_protocols_lock);
}
EXPORT_SYMBOL_GPL(gb_protocol_put);