forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
290 lines
7.2 KiB
290 lines
7.2 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* PowerNV OPAL asynchronous completion interfaces |
|
* |
|
* Copyright 2013-2017 IBM Corp. |
|
*/ |
|
|
|
#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> |
|
|
|
enum opal_async_token_state { |
|
ASYNC_TOKEN_UNALLOCATED = 0, |
|
ASYNC_TOKEN_ALLOCATED, |
|
ASYNC_TOKEN_DISPATCHED, |
|
ASYNC_TOKEN_ABANDONED, |
|
ASYNC_TOKEN_COMPLETED |
|
}; |
|
|
|
struct opal_async_token { |
|
enum opal_async_token_state state; |
|
struct opal_msg response; |
|
}; |
|
|
|
static DECLARE_WAIT_QUEUE_HEAD(opal_async_wait); |
|
static DEFINE_SPINLOCK(opal_async_comp_lock); |
|
static struct semaphore opal_async_sem; |
|
static unsigned int opal_max_async_tokens; |
|
static struct opal_async_token *opal_async_tokens; |
|
|
|
static int __opal_async_get_token(void) |
|
{ |
|
unsigned long flags; |
|
int i, token = -EBUSY; |
|
|
|
spin_lock_irqsave(&opal_async_comp_lock, flags); |
|
|
|
for (i = 0; i < opal_max_async_tokens; i++) { |
|
if (opal_async_tokens[i].state == ASYNC_TOKEN_UNALLOCATED) { |
|
opal_async_tokens[i].state = ASYNC_TOKEN_ALLOCATED; |
|
token = i; |
|
break; |
|
} |
|
} |
|
|
|
spin_unlock_irqrestore(&opal_async_comp_lock, flags); |
|
return token; |
|
} |
|
|
|
/* |
|
* Note: If the returned token is used in an opal call and opal returns |
|
* OPAL_ASYNC_COMPLETION you MUST call one of opal_async_wait_response() or |
|
* opal_async_wait_response_interruptible() at least once before calling another |
|
* opal_async_* function |
|
*/ |
|
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); |
|
|
|
static int __opal_async_release_token(int token) |
|
{ |
|
unsigned long flags; |
|
int rc; |
|
|
|
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); |
|
switch (opal_async_tokens[token].state) { |
|
case ASYNC_TOKEN_COMPLETED: |
|
case ASYNC_TOKEN_ALLOCATED: |
|
opal_async_tokens[token].state = ASYNC_TOKEN_UNALLOCATED; |
|
rc = 0; |
|
break; |
|
/* |
|
* DISPATCHED and ABANDONED tokens must wait for OPAL to respond. |
|
* Mark a DISPATCHED token as ABANDONED so that the response handling |
|
* code knows no one cares and that it can free it then. |
|
*/ |
|
case ASYNC_TOKEN_DISPATCHED: |
|
opal_async_tokens[token].state = ASYNC_TOKEN_ABANDONED; |
|
fallthrough; |
|
default: |
|
rc = 1; |
|
} |
|
spin_unlock_irqrestore(&opal_async_comp_lock, flags); |
|
|
|
return rc; |
|
} |
|
|
|
int opal_async_release_token(int token) |
|
{ |
|
int ret; |
|
|
|
ret = __opal_async_release_token(token); |
|
if (!ret) |
|
up(&opal_async_sem); |
|
|
|
return ret; |
|
} |
|
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; |
|
} |
|
|
|
/* |
|
* There is no need to mark the token as dispatched, wait_event() |
|
* will block until the token completes. |
|
* |
|
* Wakeup the poller before we wait for events to speed things |
|
* up on platforms or simulators where the interrupts aren't |
|
* functional. |
|
*/ |
|
opal_wake_poller(); |
|
wait_event(opal_async_wait, opal_async_tokens[token].state |
|
== ASYNC_TOKEN_COMPLETED); |
|
memcpy(msg, &opal_async_tokens[token].response, sizeof(*msg)); |
|
|
|
return 0; |
|
} |
|
EXPORT_SYMBOL_GPL(opal_async_wait_response); |
|
|
|
int opal_async_wait_response_interruptible(uint64_t token, struct opal_msg *msg) |
|
{ |
|
unsigned long flags; |
|
int ret; |
|
|
|
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; |
|
} |
|
|
|
/* |
|
* The first time this gets called we mark the token as DISPATCHED |
|
* so that if wait_event_interruptible() returns not zero and the |
|
* caller frees the token, we know not to actually free the token |
|
* until the response comes. |
|
* |
|
* Only change if the token is ALLOCATED - it may have been |
|
* completed even before the caller gets around to calling this |
|
* the first time. |
|
* |
|
* There is also a dirty great comment at the token allocation |
|
* function that if the opal call returns OPAL_ASYNC_COMPLETION to |
|
* the caller then the caller *must* call this or the not |
|
* interruptible version before doing anything else with the |
|
* token. |
|
*/ |
|
if (opal_async_tokens[token].state == ASYNC_TOKEN_ALLOCATED) { |
|
spin_lock_irqsave(&opal_async_comp_lock, flags); |
|
if (opal_async_tokens[token].state == ASYNC_TOKEN_ALLOCATED) |
|
opal_async_tokens[token].state = ASYNC_TOKEN_DISPATCHED; |
|
spin_unlock_irqrestore(&opal_async_comp_lock, flags); |
|
} |
|
|
|
/* |
|
* Wakeup the poller before we wait for events to speed things |
|
* up on platforms or simulators where the interrupts aren't |
|
* functional. |
|
*/ |
|
opal_wake_poller(); |
|
ret = wait_event_interruptible(opal_async_wait, |
|
opal_async_tokens[token].state == |
|
ASYNC_TOKEN_COMPLETED); |
|
if (!ret) |
|
memcpy(msg, &opal_async_tokens[token].response, sizeof(*msg)); |
|
|
|
return ret; |
|
} |
|
EXPORT_SYMBOL_GPL(opal_async_wait_response_interruptible); |
|
|
|
/* Called from interrupt context */ |
|
static int opal_async_comp_event(struct notifier_block *nb, |
|
unsigned long msg_type, void *msg) |
|
{ |
|
struct opal_msg *comp_msg = msg; |
|
enum opal_async_token_state state; |
|
unsigned long flags; |
|
uint64_t token; |
|
|
|
if (msg_type != OPAL_MSG_ASYNC_COMP) |
|
return 0; |
|
|
|
token = be64_to_cpu(comp_msg->params[0]); |
|
spin_lock_irqsave(&opal_async_comp_lock, flags); |
|
state = opal_async_tokens[token].state; |
|
opal_async_tokens[token].state = ASYNC_TOKEN_COMPLETED; |
|
spin_unlock_irqrestore(&opal_async_comp_lock, flags); |
|
|
|
if (state == ASYNC_TOKEN_ABANDONED) { |
|
/* Free the token, no one else will */ |
|
opal_async_release_token(token); |
|
return 0; |
|
} |
|
memcpy(&opal_async_tokens[token].response, comp_msg, sizeof(*comp_msg)); |
|
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, |
|
}; |
|
|
|
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: %pOF has no opal-msg-async-num\n", |
|
__func__, opal_node); |
|
err = -ENOENT; |
|
goto out_opal_node; |
|
} |
|
|
|
opal_max_async_tokens = be32_to_cpup(async); |
|
opal_async_tokens = kcalloc(opal_max_async_tokens, |
|
sizeof(*opal_async_tokens), GFP_KERNEL); |
|
if (!opal_async_tokens) { |
|
err = -ENOMEM; |
|
goto out_opal_node; |
|
} |
|
|
|
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); |
|
kfree(opal_async_tokens); |
|
goto out_opal_node; |
|
} |
|
|
|
sema_init(&opal_async_sem, opal_max_async_tokens); |
|
|
|
out_opal_node: |
|
of_node_put(opal_node); |
|
out: |
|
return err; |
|
}
|
|
|