The mailbox drivers are fragmented, and some implement their own core. Unify the drivers and implement common functionality in a framework. Signed-off-by: Courtney Cavin <courtney.cavin@xxxxxxxxxxxxxx> --- drivers/mailbox/Makefile | 1 + drivers/mailbox/core.c | 573 +++++++++++++++++++++++++++++++++++++++++++++++ include/linux/mbox.h | 175 +++++++++++++++ 3 files changed, 749 insertions(+) create mode 100644 drivers/mailbox/core.c create mode 100644 include/linux/mbox.h diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile index e0facb3..53712ed 100644 --- a/drivers/mailbox/Makefile +++ b/drivers/mailbox/Makefile @@ -1,3 +1,4 @@ +obj-$(CONFIG_MAILBOX) += core.o obj-$(CONFIG_PL320_MBOX) += pl320-ipc.o obj-$(CONFIG_OMAP_MBOX) += omap-mailbox.o diff --git a/drivers/mailbox/core.c b/drivers/mailbox/core.c new file mode 100644 index 0000000..0dc865e --- /dev/null +++ b/drivers/mailbox/core.c @@ -0,0 +1,573 @@ +/* + * Generic mailbox implementation + * + * Copyright (C) 2014 Sony Mobile Communications, AB. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + */ + +#include <linux/module.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/mbox.h> + +static DEFINE_MUTEX(mbox_lock); +static LIST_HEAD(mbox_adapters); + +static DEFINE_MUTEX(mbox_lookup_lock); +static LIST_HEAD(mbox_lookup_list); + +static int __mbox_adapter_request(struct mbox_adapter *adap, + struct mbox_channel *chan) +{ + int rc; + + if (chan->users > 0) { + chan->users++; + return 0; + } + + if (!try_module_get(adap->ops->owner)) + return -ENODEV; + + if (adap->ops->request) { + rc = adap->ops->request(adap, chan); + if (rc) { + module_put(adap->ops->owner); + return rc; + } + } + + chan->users++; + + return 0; +} + +static void __mbox_adapter_release(struct mbox_adapter *adap, + struct mbox_channel *chan) +{ + if (!adap || !chan) + return; + + if (chan->users == 0) { + dev_err(adap->dev, "device already released\n"); + return; + } + + chan->users--; + if (chan->users > 0) + return; + + if (adap->ops->release) + adap->ops->release(adap, chan); + module_put(adap->ops->owner); +} + +static struct mbox_channel * +mbox_adapter_request_channel(struct mbox_adapter *adap, unsigned int index) +{ + struct mbox_channel *chan; + int rc; + + if (!adap || index >= adap->nchannels) + return ERR_PTR(-EINVAL); + + mutex_lock(&adap->lock); + chan = &adap->channels[index]; + + rc = __mbox_adapter_request(adap, chan); + if (rc) + chan = ERR_PTR(rc); + mutex_unlock(&adap->lock); + + return chan; +} + +static void mbox_adapter_release_channel(struct mbox_adapter *adap, + struct mbox_channel *chan) +{ + if (!adap || !chan) + return; + + mutex_lock(&adap->lock); + __mbox_adapter_release(adap, chan); + mutex_unlock(&adap->lock); +} + +static int of_mbox_simple_xlate(struct mbox_adapter *adap, + const struct of_phandle_args *args) +{ + if (adap->of_n_cells < 1) + return -EINVAL; + if (args->args[0] >= adap->nchannels) + return -EINVAL; + + return args->args[0]; +} + +static struct mbox_adapter *of_node_to_mbox_adapter(struct device_node *np) +{ + struct mbox_adapter *adap; + + mutex_lock(&mbox_lock); + list_for_each_entry(adap, &mbox_adapters, list) { + if (adap->dev && adap->dev->of_node == np) { + mutex_unlock(&mbox_lock); + return adap; + } + } + mutex_unlock(&mbox_lock); + + return ERR_PTR(-EPROBE_DEFER); +} + +static void of_mbox_adapter_add(struct mbox_adapter *adap) +{ + if (!adap->dev) + return; + + if (!adap->of_xlate) { + adap->of_xlate = of_mbox_simple_xlate; + adap->of_n_cells = 1; + } + + of_node_get(adap->dev->of_node); +} + +static void of_mbox_adapter_remove(struct mbox_adapter *adap) +{ + if (!adap->dev) + return; + of_node_put(adap->dev->of_node); +} + +static struct mbox_channel * +of_mbox_adapter_request_channel(struct device_node *np, const char *con_id) +{ + struct of_phandle_args args; + struct mbox_adapter *adap; + struct mbox_channel *chan; + int index = 0; + int rc; + + if (con_id) { + index = of_property_match_string(np, "mbox-names", con_id); + if (index < 0) + return ERR_PTR(index); + } + + rc = of_parse_phandle_with_args(np, "mbox", "#mbox-cells", + index, &args); + if (rc) + return ERR_PTR(rc); + + adap = of_node_to_mbox_adapter(args.np); + if (IS_ERR(adap)) { + chan = ERR_CAST(adap); + goto out; + } + + if (args.args_count != adap->of_n_cells) { + chan = ERR_PTR(-EINVAL); + goto out; + } + + index = adap->of_xlate(adap, &args); + if (index < 0) { + chan = ERR_PTR(index); + goto out; + } + + chan = mbox_adapter_request_channel(adap, index); + +out: + of_node_put(args.np); + return chan; +} + +/** + * mbox_adapter_add() - register a new MBOX adapter + * @adap: the adapter to add + */ +int mbox_adapter_add(struct mbox_adapter *adap) +{ + struct mbox_channel *chan; + unsigned int i; + + if (!adap || !adap->dev || !adap->ops || !adap->ops->put_message) + return -EINVAL; + if (adap->nchannels == 0) + return -EINVAL; + + adap->channels = kzalloc(adap->nchannels * sizeof(*chan), GFP_KERNEL); + if (!adap->channels) + return -ENOMEM; + + for (i = 0; i < adap->nchannels; ++i) { + chan = &adap->channels[i]; + ATOMIC_INIT_NOTIFIER_HEAD(&chan->notifier); + chan->adapter = adap; + chan->id = i; + } + mutex_init(&adap->lock); + + mutex_lock(&mbox_lock); + list_add(&adap->list, &mbox_adapters); + + of_mbox_adapter_add(adap); + mutex_unlock(&mbox_lock); + + return 0; +} +EXPORT_SYMBOL(mbox_adapter_add); + +/** + * mbox_adapter_remove() - unregisters a MBOX adapter + * @adap: the adapter to remove + * + * This function may return -EBUSY if the adapter provides a channel which + * is still requested. + */ +int mbox_adapter_remove(struct mbox_adapter *adap) +{ + unsigned int i; + + mutex_lock(&mbox_lock); + + for (i = 0; i < adap->nchannels; ++i) { + struct mbox_channel *chan = &adap->channels[i]; + if (chan->users) { + mutex_unlock(&mbox_lock); + return -EBUSY; + } + } + list_del_init(&adap->list); + + of_mbox_adapter_remove(adap); + + mutex_unlock(&mbox_lock); + + kfree(adap->channels); + + return 0; +} +EXPORT_SYMBOL(mbox_adapter_remove); + +static int mbox_channel_put_message(struct mbox_channel *chan, + const void *data, unsigned int len) +{ + int rc; + + mutex_lock(&chan->adapter->lock); + rc = chan->adapter->ops->put_message(chan->adapter, chan, data, len); + mutex_unlock(&chan->adapter->lock); + + return rc; +} + +/** + * mbox_channel_notify() - notify the core that a channel has a message + * @chan: the channel which has data + * @data: the location of said data + * @len: the length of specified data + * + * This function may be called from interrupt/no-sleep context. + */ +int mbox_channel_notify(struct mbox_channel *chan, + const void *data, unsigned int len) +{ + return atomic_notifier_call_chain(&chan->notifier, len, (void *)data); +} +EXPORT_SYMBOL(mbox_channel_notify); + +/** + * mbox_add_table() - add a lookup table for adapter consumers + * @table: array of consumers to register + * @num: number of consumers in array + */ +void __init mbox_add_table(struct mbox_lookup *table, unsigned int num) +{ + mutex_lock(&mbox_lookup_lock); + while (num--) { + if (table->provider && (table->dev_id || table->con_id)) + list_add_tail(&table->list, &mbox_lookup_list); + table++; + } + mutex_unlock(&mbox_lookup_lock); +} +EXPORT_SYMBOL(mbox_add_table); + +static struct mbox_adapter *mbox_adapter_lookup(const char *name) +{ + struct mbox_adapter *adap; + + if (!name) + return ERR_PTR(-EINVAL); + + mutex_lock(&mbox_lock); + list_for_each_entry(adap, &mbox_adapters, list) { + const char *aname = dev_name(adap->dev); + if (aname && !strcmp(aname, name)) { + mutex_unlock(&mbox_lock); + return adap; + } + } + mutex_unlock(&mbox_lock); + + return ERR_PTR(-ENODEV); +} + +static struct mbox_adapter * +mbox_channel_lookup(struct device *dev, const char *con_id, int *index) +{ + const char *dev_id = dev ? dev_name(dev) : NULL; + struct mbox_adapter *adap = ERR_PTR(-ENODEV); + struct mbox_lookup *lp; + int match, best; + + best = 0; + mutex_lock(&mbox_lookup_lock); + list_for_each_entry(lp, &mbox_lookup_list, list) { + match = 0; + if (lp->dev_id) { + if (!dev_id || strcmp(lp->dev_id, dev_id)) + continue; + match += 2; + } + if (lp->con_id) { + if (!con_id || strcmp(lp->con_id, con_id)) + continue; + match += 1; + } + if (match <= best) + continue; + adap = mbox_adapter_lookup(lp->provider); + if (IS_ERR(adap)) + continue; + if (index) + *index = lp->index; + if (match == 3) + break; + best = match; + } + mutex_unlock(&mbox_lookup_lock); + + return adap; +} + +/** + * struct mbox - MBOX channel consumer + * @chan: internal MBOX channel + * @nb: notifier to call on message available + */ +struct mbox { + struct mbox_channel *chan; + struct notifier_block *nb; +}; + +static struct mbox * +__mbox_alloc(struct mbox_channel *chan, struct notifier_block *nb) +{ + struct mbox *mbox; + + mbox = kzalloc(sizeof(*mbox), GFP_KERNEL); + if (mbox == NULL) + return ERR_PTR(-ENOMEM); + mbox->chan = chan; + mbox->nb = nb; + + if (mbox->nb) + atomic_notifier_chain_register(&chan->notifier, mbox->nb); + + return mbox; +} + +/** + * mbox_request() - lookup and request a MBOX channel + * @dev: device for channel consumer + * @con_id: consumer name + * @nb: notifier block used for receiving messages + * + * The notifier is called as atomic on new messages, so you may not sleep + * in the notifier callback function. + */ +struct mbox *mbox_request(struct device *dev, const char *con_id, + struct notifier_block *nb) +{ + struct mbox_adapter *adap; + struct mbox_channel *chan; + struct mbox *mbox; + int index = 0; + + if (IS_ENABLED(CONFIG_OF) && dev && dev->of_node) + return of_mbox_request(dev->of_node, con_id, nb); + + adap = mbox_channel_lookup(dev, con_id, &index); + if (IS_ERR(adap)) + return ERR_CAST(adap); + + chan = mbox_adapter_request_channel(adap, index); + if (IS_ERR(chan)) + return ERR_CAST(chan); + + mbox = __mbox_alloc(chan, nb); + if (IS_ERR(mbox)) + mbox_adapter_release_channel(adap, chan); + return mbox; +} +EXPORT_SYMBOL(mbox_request); + +/** + * mbox_release() - release a MBOX channel + * @mbox: the channel to release + * + * This releases a channel previously acquired by mbox_request(). Do not call + * this function on devm-allocated MBOX channels. + */ +void mbox_release(struct mbox *mbox) +{ + struct mbox_channel *chan = mbox->chan; + if (mbox->nb) + atomic_notifier_chain_unregister(&chan->notifier, mbox->nb); + mbox_adapter_release_channel(chan->adapter, chan); + kfree(mbox); +} +EXPORT_SYMBOL(mbox_release); + +/** + * mbox_put_message() - post a message to the MBOX channel + * @mbox: the channel to post to + * @data: location of the message + * @len: length of the message + * + * This function might sleep, and may not be called from interrupt context. + */ +int mbox_put_message(struct mbox *mbox, const void *data, unsigned int len) +{ + might_sleep(); + return mbox_channel_put_message(mbox->chan, data, len); +} +EXPORT_SYMBOL(mbox_put_message); + +/** + * of_mbox_request() - lookup in DT and request a MBOX channel + * @np: device node for lookup + * @con_id: consumer id + * @nb: notifier block used for receiving messages + * + * The notifier is called as atomic on new messages, so you may not sleep + * in the notifier callback function. + */ +struct mbox *of_mbox_request(struct device_node *np, const char *con_id, + struct notifier_block *nb) +{ + struct mbox_channel *chan; + struct mbox *mbox; + + chan = of_mbox_adapter_request_channel(np, con_id); + if (IS_ERR(chan)) + return ERR_CAST(chan); + + mbox = __mbox_alloc(chan, nb); + if (IS_ERR(mbox)) + mbox_adapter_release_channel(chan->adapter, chan); + return mbox; +} +EXPORT_SYMBOL(of_mbox_request); + +static int devm_mbox_match(struct device *dev, void *res, void *data) +{ + struct mbox **p = res; + + if (WARN_ON(!p || !*p)) + return 0; + + return *p == data; +} + +static void __devm_mbox_release(struct device *dev, void *res) +{ + mbox_release(*(struct mbox **)res); +} + +/** + * devm_mbox_release() - resource managed mbox_release() + * @dev: device for channel consumer + * @mbox: MBOX channel + * + * Release a MBOX channel previously allocated using devm_mbox_request() or + * devm_of_mbox_request(). Calling this function is usually not necessary, + * as devm-allocated resources are automatically released on driver detach. + */ +void devm_mbox_release(struct device *dev, struct mbox *mbox) +{ + WARN_ON(devres_release(dev, __devm_mbox_release, + devm_mbox_match, mbox)); +} +EXPORT_SYMBOL(devm_mbox_release); + +/** + * devm_mbox_request() - resource managed mbox_request() + * @dev: device for channel consumer + * @con_id: consumer id + * @nb: notifier block used for receiving messages + * + * This function behaves like mbox_request() but the acquired channel + * will be automatically released on driver detach. + */ +struct mbox *devm_mbox_request(struct device *dev, const char *con_id, + struct notifier_block *nb) +{ + struct mbox **ptr; + struct mbox *mbox; + + ptr = devres_alloc(__devm_mbox_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + mbox = mbox_request(dev, con_id, nb); + if (!IS_ERR(mbox)) { + *ptr = mbox; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + return mbox; +} +EXPORT_SYMBOL(devm_mbox_request); + +/** + * devm_mbox_request() - resource managed of_mbox_request() + * @dev: device for channel consumer + * @np: device node for lookup + * @con_id: consumer id + * @nb: notifier block used for receiving messages + * + * This function behaves like of_mbox_request() but the acquired channel + * will be automatically released on driver detach. + */ +struct mbox *devm_of_mbox_request(struct device *dev, struct device_node *np, + const char *con_id, struct notifier_block *nb) +{ + struct mbox **ptr; + struct mbox *mbox; + + ptr = devres_alloc(__devm_mbox_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + mbox = of_mbox_request(np, con_id, nb); + if (!IS_ERR(mbox)) { + *ptr = mbox; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + return mbox; +} +EXPORT_SYMBOL(devm_of_mbox_request); diff --git a/include/linux/mbox.h b/include/linux/mbox.h new file mode 100644 index 0000000..d577b24 --- /dev/null +++ b/include/linux/mbox.h @@ -0,0 +1,175 @@ +#ifndef __LINUX_MBOX_H +#define __LINUX_MBOX_H + +#include <linux/notifier.h> +#include <linux/err.h> +#include <linux/of.h> + +struct mbox_adapter; +struct mbox_channel; + +/** + * struct mbox_adapter_ops - MBOX adapter operations + * @put_message: hook for putting messages in the channels MBOX + * @request: optional hook for requesting an MBOX channel + * @release: optional hook for releasing an MBOX channel + * @owner: helps prevent removal of modules exporting active MBOX channels + */ +struct mbox_adapter_ops { + int (*put_message)(struct mbox_adapter *, struct mbox_channel *, + const void *, unsigned int); + int (*request)(struct mbox_adapter *, struct mbox_channel *); + int (*release)(struct mbox_adapter *, struct mbox_channel *); + struct module *owner; +}; + +struct mbox_channel { + unsigned int id; + unsigned int users; + struct mbox_adapter *adapter; + struct atomic_notifier_head notifier; +}; + +/** + * struct mbox_adapter - MBOX adapter abstraction + * @dev: device providing MBOX channels + * @ops: callback hooks for this adapter + * @nchannels: number of MBOX channels controlled by this adapter + * @channels: array of MBOX channels managed internally + * @of_xlate: OF translation hook for DT lookups + * @of_n_cells: number of cells for DT lookups + * @list: list node for internal use + * @lock: mutex for internal use + */ +struct mbox_adapter { + struct device *dev; + const struct mbox_adapter_ops *ops; + unsigned int nchannels; + struct mbox_channel *channels; + int (*of_xlate)(struct mbox_adapter *, + const struct of_phandle_args *args); + unsigned int of_n_cells; + struct list_head list; + struct mutex lock; +}; + +#if IS_ENABLED(CONFIG_MAILBOX) + +int mbox_adapter_add(struct mbox_adapter *adap); +int mbox_adapter_remove(struct mbox_adapter *adap); + +int mbox_channel_notify(struct mbox_channel *chan, + const void *data, unsigned int len); + +#else + +static inline int mbox_adapter_add(struct mbox_adapter *adap) +{ + return -EINVAL; +} +static inline int mbox_adapter_remove(struct mbox_adapter *adap) +{ + return -EINVAL; +} + +static inline int mbox_channel_notify(struct mbox_channel *chan, + const void *data, unsigned int len) +{ + return -EINVAL; +} + +#endif + +struct mbox; + +#if IS_ENABLED(CONFIG_MAILBOX) + +struct mbox *mbox_request(struct device *dev, const char *con_id, + struct notifier_block *nb); +void mbox_release(struct mbox *mbox); + +int mbox_put_message(struct mbox *mbox, const void *data, unsigned int len); + +struct mbox *of_mbox_request(struct device_node *np, const char *con_id, + struct notifier_block *nb); + +struct mbox *devm_mbox_request(struct device *dev, const char *con_id, + struct notifier_block *nb); + +struct mbox *devm_of_mbox_request(struct device *dev, struct device_node *np, + const char *con_id, struct notifier_block *nb); + +void devm_mbox_release(struct device *dev, struct mbox *mbox); + +#else + +static inline struct mbox *mbox_request(struct device *dev, const char *con_id, + struct notifier_block *nb) +{ + return ERR_PTR(-ENODEV); +} + +void mbox_release(struct mbox *mbox) +{ +} + +static inline int mbox_put_message(struct mbox *mbox, + const void *data, unsigned int len) +{ + return -EINVAL; +} + +static inline struct mbox *of_mbox_request(struct device_node *np, + const char *con_id, struct notifier_block *nb) +{ + return ERR_PTR(-ENODEV); +} + +static inline struct mbox *devm_mbox_request(struct device *dev, + const char *con_id, struct notifier_block *nb) +{ + return ERR_PTR(-ENODEV); +} + +static inline struct mbox *devm_of_mbox_request(struct device *dev, + struct device_node *np, const char *con_id, + struct notifier_block *nb) +{ + return ERR_PTR(-ENODEV); +} + +static inline void devm_mbox_release(struct device *dev, struct mbox *mbox) +{ +} + +#endif + +struct mbox_lookup { + struct list_head list; + const char *provider; + unsigned int index; + const char *dev_id; + const char *con_id; +}; + +#define MBOX_LOOKUP(_provider, _index, _dev_id, _con_id) \ + { \ + .provider = _provider, \ + .index = _index, \ + .dev_id = _dev_id, \ + .con_id = _con_id, \ + } + +#if IS_ENABLED(CONFIG_MAILBOX) + +void mbox_add_table(struct mbox_lookup *table, unsigned int num); + +#else + +static inline void mbox_add_table(struct mbox_lookup *table, unsigned int num) +{ +} + +#endif + +#endif /* __LINUX_MBOX_H */ -- 1.8.1.5 -- To unsubscribe from this list: send the line "unsubscribe linux-doc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html