/* * PowerNV OPAL asynchronous completion interfaces * * Copyright 2013 IBM Corp. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. */ #undef DEBUG #include <linux/kernel.h> #include <linux/init.h> #include <linux/slab.h> #include <linux/sched.h> #include <linux/semaphore.h> #include <linux/spinlock.h> #include <linux/wait.h> #include <linux/gfp.h> #include <linux/of.h> #include <asm/machdep.h> #include <asm/opal.h> #define N_ASYNC_COMPLETIONS 64 static DECLARE_BITMAP(opal_async_complete_map, N_ASYNC_COMPLETIONS) = {~0UL}; static DECLARE_BITMAP(opal_async_token_map, N_ASYNC_COMPLETIONS); static DECLARE_WAIT_QUEUE_HEAD(opal_async_wait); static DEFINE_SPINLOCK(opal_async_comp_lock); static struct semaphore opal_async_sem; static struct opal_msg *opal_async_responses; static unsigned int opal_max_async_tokens; int __opal_async_get_token(void) { unsigned long flags; int token; spin_lock_irqsave(&opal_async_comp_lock, flags); token = find_first_bit(opal_async_complete_map, opal_max_async_tokens); if (token >= opal_max_async_tokens) { token = -EBUSY; goto out; } if (__test_and_set_bit(token, opal_async_token_map)) { token = -EBUSY; goto out; } __clear_bit(token, opal_async_complete_map); out: spin_unlock_irqrestore(&opal_async_comp_lock, flags); return token; } int opal_async_get_token_interruptible(void) { int token; /* Wait until a token is available */ if (down_interruptible(&opal_async_sem)) return -ERESTARTSYS; token = __opal_async_get_token(); if (token < 0) up(&opal_async_sem); return token; } EXPORT_SYMBOL_GPL(opal_async_get_token_interruptible); int __opal_async_release_token(int token) { unsigned long flags; if (token < 0 || token >= opal_max_async_tokens) { pr_err("%s: Passed token is out of range, token %d\n", __func__, token); return -EINVAL; } spin_lock_irqsave(&opal_async_comp_lock, flags); __set_bit(token, opal_async_complete_map); __clear_bit(token, opal_async_token_map); spin_unlock_irqrestore(&opal_async_comp_lock, flags); return 0; } int opal_async_release_token(int token) { int ret; ret = __opal_async_release_token(token); if (ret) return ret; up(&opal_async_sem); return 0; } EXPORT_SYMBOL_GPL(opal_async_release_token); int opal_async_wait_response(uint64_t token, struct opal_msg *msg) { if (token >= opal_max_async_tokens) { pr_err("%s: Invalid token passed\n", __func__); return -EINVAL; } if (!msg) { pr_err("%s: Invalid message pointer passed\n", __func__); return -EINVAL; } wait_event(opal_async_wait, test_bit(token, opal_async_complete_map)); memcpy(msg, &opal_async_responses[token], sizeof(*msg)); return 0; } EXPORT_SYMBOL_GPL(opal_async_wait_response); static int opal_async_comp_event(struct notifier_block *nb, unsigned long msg_type, void *msg) { struct opal_msg *comp_msg = msg; unsigned long flags; uint64_t token; if (msg_type != OPAL_MSG_ASYNC_COMP) return 0; token = be64_to_cpu(comp_msg->params[0]); memcpy(&opal_async_responses[token], comp_msg, sizeof(*comp_msg)); spin_lock_irqsave(&opal_async_comp_lock, flags); __set_bit(token, opal_async_complete_map); spin_unlock_irqrestore(&opal_async_comp_lock, flags); wake_up(&opal_async_wait); return 0; } static struct notifier_block opal_async_comp_nb = { .notifier_call = opal_async_comp_event, .next = NULL, .priority = 0, }; static int __init opal_async_comp_init(void) { struct device_node *opal_node; const __be32 *async; int err; opal_node = of_find_node_by_path("/ibm,opal"); if (!opal_node) { pr_err("%s: Opal node not found\n", __func__); err = -ENOENT; goto out; } async = of_get_property(opal_node, "opal-msg-async-num", NULL); if (!async) { pr_err("%s: %s has no opal-msg-async-num\n", __func__, opal_node->full_name); err = -ENOENT; goto out_opal_node; } opal_max_async_tokens = be32_to_cpup(async); if (opal_max_async_tokens > N_ASYNC_COMPLETIONS) opal_max_async_tokens = N_ASYNC_COMPLETIONS; err = opal_message_notifier_register(OPAL_MSG_ASYNC_COMP, &opal_async_comp_nb); if (err) { pr_err("%s: Can't register OPAL event notifier (%d)\n", __func__, err); goto out_opal_node; } opal_async_responses = kzalloc( sizeof(*opal_async_responses) * opal_max_async_tokens, GFP_KERNEL); if (!opal_async_responses) { pr_err("%s: Out of memory, failed to do asynchronous " "completion init\n", __func__); err = -ENOMEM; goto out_opal_node; } /* Initialize to 1 less than the maximum tokens available, as we may * require to pop one during emergency through synchronous call to * __opal_async_get_token() */ sema_init(&opal_async_sem, opal_max_async_tokens - 1); out_opal_node: of_node_put(opal_node); out: return err; } machine_subsys_initcall(powernv, opal_async_comp_init);