| /* |
| * Greybus protocol handling |
| * |
| * Copyright 2014 Google Inc. |
| * |
| * Released under the GPLv2 only. |
| */ |
| |
| #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; |
| } |
| |
| /* Returns true if protocol was succesfully registered, false otherwise */ |
| bool gb_protocol_register(struct gb_protocol *protocol) |
| { |
| struct gb_protocol *existing; |
| u8 id = protocol->id; |
| u8 major = protocol->major; |
| u8 minor = protocol->minor; |
| |
| /* |
| * 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 false; |
| } |
| |
| /* |
| * 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); |
| |
| return true; |
| } |
| |
| /* |
| * De-register a previously registered protocol. |
| * |
| * XXX Currently this fails (and reports an error to the caller) if |
| * XXX the protocol is currently in use. We may want to forcefully |
| * XXX kill off a protocol and all its active users at some point. |
| * XXX But I think that's better handled by quescing modules that |
| * XXX have users and having those users drop their reference. |
| * |
| * Returns true if successful, false otherwise. |
| */ |
| bool gb_protocol_deregister(struct gb_protocol *protocol) |
| { |
| u8 protocol_count = 0; |
| |
| spin_lock_irq(&gb_protocols_lock); |
| protocol = _gb_protocol_find(protocol->id, protocol->major, |
| protocol->minor); |
| if (protocol) { |
| protocol_count = protocol->count; |
| if (!protocol_count) |
| list_del(&protocol->links); |
| } |
| spin_unlock_irq(&gb_protocols_lock); |
| |
| return protocol && !protocol_count; |
| } |
| |
| /* 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) { |
| 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); |
| else |
| pr_err("protocol id %hhu version %hhu.%hhu not found\n", |
| id, major, minor); |
| |
| return protocol; |
| } |
| |
| void gb_protocol_put(struct gb_protocol *protocol) |
| { |
| u8 major = protocol->major; |
| u8 minor = protocol->minor; |
| u8 protocol_count; |
| |
| spin_lock_irq(&gb_protocols_lock); |
| protocol = _gb_protocol_find(protocol->id, protocol->major, |
| protocol->minor); |
| if (protocol) { |
| protocol_count = protocol->count; |
| if (protocol_count) |
| protocol->count--; |
| } |
| spin_unlock_irq(&gb_protocols_lock); |
| if (protocol) |
| WARN_ON(!protocol_count); |
| else |
| pr_err("protocol id %hhu version %hhu.%hhu not found\n", |
| protocol->id, major, minor); |
| } |
| |
| bool gb_protocol_init(void) |
| { |
| bool ret = true; |
| |
| if (!gb_battery_protocol_init()) { |
| pr_err("error initializing battery protocol\n"); |
| ret = false; |
| } |
| if (!gb_gpio_protocol_init()) { |
| pr_err("error initializing gpio protocol\n"); |
| ret = false; |
| } |
| if (!gb_i2c_protocol_init()) { |
| pr_err("error initializing i2c protocol\n"); |
| ret = false; |
| } |
| if (!gb_uart_protocol_init()) { |
| pr_err("error initializing uart protocol\n"); |
| ret = false; |
| } |
| if (!gb_sdio_protocol_init()) { |
| pr_err("error initializing sdio protocol\n"); |
| ret = false; |
| } |
| return ret; |
| } |
| |
| void gb_protocol_exit(void) |
| { |
| gb_sdio_protocol_exit(); |
| gb_uart_protocol_exit(); |
| gb_i2c_protocol_exit(); |
| gb_gpio_protocol_exit(); |
| gb_battery_protocol_exit(); |
| } |