![Philip J Kelleher](/assets/img/avatar_default.png)
Changes in v2 include: o Fixed spelling of guarantee. o Fixed potential memory leak if slot reset fails out. o Changed list_for_each_entry_safe with list_for_each_entry. Signed-off-by: Philip J Kelleher <pjk1939@linux.vnet.ibm.com> Signed-off-by: Jens Axboe <axboe@kernel.dk>
791 lines
18 KiB
C
791 lines
18 KiB
C
/*
|
|
* Filename: cregs.c
|
|
*
|
|
*
|
|
* Authors: Joshua Morris <josh.h.morris@us.ibm.com>
|
|
* Philip Kelleher <pjk1939@linux.vnet.ibm.com>
|
|
*
|
|
* (C) Copyright 2013 IBM Corporation
|
|
*
|
|
* 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.
|
|
*
|
|
* This program is distributed in the hope that 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <linux/completion.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "rsxx_priv.h"
|
|
|
|
#define CREG_TIMEOUT_MSEC 10000
|
|
|
|
typedef void (*creg_cmd_cb)(struct rsxx_cardinfo *card,
|
|
struct creg_cmd *cmd,
|
|
int st);
|
|
|
|
struct creg_cmd {
|
|
struct list_head list;
|
|
creg_cmd_cb cb;
|
|
void *cb_private;
|
|
unsigned int op;
|
|
unsigned int addr;
|
|
int cnt8;
|
|
void *buf;
|
|
unsigned int stream;
|
|
unsigned int status;
|
|
};
|
|
|
|
static struct kmem_cache *creg_cmd_pool;
|
|
|
|
|
|
/*------------ Private Functions --------------*/
|
|
|
|
#if defined(__LITTLE_ENDIAN)
|
|
#define LITTLE_ENDIAN 1
|
|
#elif defined(__BIG_ENDIAN)
|
|
#define LITTLE_ENDIAN 0
|
|
#else
|
|
#error Unknown endianess!!! Aborting...
|
|
#endif
|
|
|
|
static int copy_to_creg_data(struct rsxx_cardinfo *card,
|
|
int cnt8,
|
|
void *buf,
|
|
unsigned int stream)
|
|
{
|
|
int i = 0;
|
|
u32 *data = buf;
|
|
|
|
if (unlikely(card->eeh_state))
|
|
return -EIO;
|
|
|
|
for (i = 0; cnt8 > 0; i++, cnt8 -= 4) {
|
|
/*
|
|
* Firmware implementation makes it necessary to byte swap on
|
|
* little endian processors.
|
|
*/
|
|
if (LITTLE_ENDIAN && stream)
|
|
iowrite32be(data[i], card->regmap + CREG_DATA(i));
|
|
else
|
|
iowrite32(data[i], card->regmap + CREG_DATA(i));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int copy_from_creg_data(struct rsxx_cardinfo *card,
|
|
int cnt8,
|
|
void *buf,
|
|
unsigned int stream)
|
|
{
|
|
int i = 0;
|
|
u32 *data = buf;
|
|
|
|
if (unlikely(card->eeh_state))
|
|
return -EIO;
|
|
|
|
for (i = 0; cnt8 > 0; i++, cnt8 -= 4) {
|
|
/*
|
|
* Firmware implementation makes it necessary to byte swap on
|
|
* little endian processors.
|
|
*/
|
|
if (LITTLE_ENDIAN && stream)
|
|
data[i] = ioread32be(card->regmap + CREG_DATA(i));
|
|
else
|
|
data[i] = ioread32(card->regmap + CREG_DATA(i));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void creg_issue_cmd(struct rsxx_cardinfo *card, struct creg_cmd *cmd)
|
|
{
|
|
int st;
|
|
|
|
if (unlikely(card->eeh_state))
|
|
return;
|
|
|
|
iowrite32(cmd->addr, card->regmap + CREG_ADD);
|
|
iowrite32(cmd->cnt8, card->regmap + CREG_CNT);
|
|
|
|
if (cmd->op == CREG_OP_WRITE) {
|
|
if (cmd->buf) {
|
|
st = copy_to_creg_data(card, cmd->cnt8,
|
|
cmd->buf, cmd->stream);
|
|
if (st)
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (unlikely(card->eeh_state))
|
|
return;
|
|
|
|
/* Setting the valid bit will kick off the command. */
|
|
iowrite32(cmd->op, card->regmap + CREG_CMD);
|
|
}
|
|
|
|
static void creg_kick_queue(struct rsxx_cardinfo *card)
|
|
{
|
|
if (card->creg_ctrl.active || list_empty(&card->creg_ctrl.queue))
|
|
return;
|
|
|
|
card->creg_ctrl.active = 1;
|
|
card->creg_ctrl.active_cmd = list_first_entry(&card->creg_ctrl.queue,
|
|
struct creg_cmd, list);
|
|
list_del(&card->creg_ctrl.active_cmd->list);
|
|
card->creg_ctrl.q_depth--;
|
|
|
|
/*
|
|
* We have to set the timer before we push the new command. Otherwise,
|
|
* we could create a race condition that would occur if the timer
|
|
* was not canceled, and expired after the new command was pushed,
|
|
* but before the command was issued to hardware.
|
|
*/
|
|
mod_timer(&card->creg_ctrl.cmd_timer,
|
|
jiffies + msecs_to_jiffies(CREG_TIMEOUT_MSEC));
|
|
|
|
creg_issue_cmd(card, card->creg_ctrl.active_cmd);
|
|
}
|
|
|
|
static int creg_queue_cmd(struct rsxx_cardinfo *card,
|
|
unsigned int op,
|
|
unsigned int addr,
|
|
unsigned int cnt8,
|
|
void *buf,
|
|
int stream,
|
|
creg_cmd_cb callback,
|
|
void *cb_private)
|
|
{
|
|
struct creg_cmd *cmd;
|
|
|
|
/* Don't queue stuff up if we're halted. */
|
|
if (unlikely(card->halt))
|
|
return -EINVAL;
|
|
|
|
if (card->creg_ctrl.reset)
|
|
return -EAGAIN;
|
|
|
|
if (cnt8 > MAX_CREG_DATA8)
|
|
return -EINVAL;
|
|
|
|
cmd = kmem_cache_alloc(creg_cmd_pool, GFP_KERNEL);
|
|
if (!cmd)
|
|
return -ENOMEM;
|
|
|
|
INIT_LIST_HEAD(&cmd->list);
|
|
|
|
cmd->op = op;
|
|
cmd->addr = addr;
|
|
cmd->cnt8 = cnt8;
|
|
cmd->buf = buf;
|
|
cmd->stream = stream;
|
|
cmd->cb = callback;
|
|
cmd->cb_private = cb_private;
|
|
cmd->status = 0;
|
|
|
|
spin_lock_bh(&card->creg_ctrl.lock);
|
|
list_add_tail(&cmd->list, &card->creg_ctrl.queue);
|
|
card->creg_ctrl.q_depth++;
|
|
creg_kick_queue(card);
|
|
spin_unlock_bh(&card->creg_ctrl.lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void creg_cmd_timed_out(unsigned long data)
|
|
{
|
|
struct rsxx_cardinfo *card = (struct rsxx_cardinfo *) data;
|
|
struct creg_cmd *cmd;
|
|
|
|
spin_lock(&card->creg_ctrl.lock);
|
|
cmd = card->creg_ctrl.active_cmd;
|
|
card->creg_ctrl.active_cmd = NULL;
|
|
spin_unlock(&card->creg_ctrl.lock);
|
|
|
|
if (cmd == NULL) {
|
|
card->creg_ctrl.creg_stats.creg_timeout++;
|
|
dev_warn(CARD_TO_DEV(card),
|
|
"No active command associated with timeout!\n");
|
|
return;
|
|
}
|
|
|
|
if (cmd->cb)
|
|
cmd->cb(card, cmd, -ETIMEDOUT);
|
|
|
|
kmem_cache_free(creg_cmd_pool, cmd);
|
|
|
|
|
|
spin_lock(&card->creg_ctrl.lock);
|
|
card->creg_ctrl.active = 0;
|
|
creg_kick_queue(card);
|
|
spin_unlock(&card->creg_ctrl.lock);
|
|
}
|
|
|
|
|
|
static void creg_cmd_done(struct work_struct *work)
|
|
{
|
|
struct rsxx_cardinfo *card;
|
|
struct creg_cmd *cmd;
|
|
int st = 0;
|
|
|
|
card = container_of(work, struct rsxx_cardinfo,
|
|
creg_ctrl.done_work);
|
|
|
|
/*
|
|
* The timer could not be cancelled for some reason,
|
|
* race to pop the active command.
|
|
*/
|
|
if (del_timer_sync(&card->creg_ctrl.cmd_timer) == 0)
|
|
card->creg_ctrl.creg_stats.failed_cancel_timer++;
|
|
|
|
spin_lock_bh(&card->creg_ctrl.lock);
|
|
cmd = card->creg_ctrl.active_cmd;
|
|
card->creg_ctrl.active_cmd = NULL;
|
|
spin_unlock_bh(&card->creg_ctrl.lock);
|
|
|
|
if (cmd == NULL) {
|
|
dev_err(CARD_TO_DEV(card),
|
|
"Spurious creg interrupt!\n");
|
|
return;
|
|
}
|
|
|
|
card->creg_ctrl.creg_stats.stat = ioread32(card->regmap + CREG_STAT);
|
|
cmd->status = card->creg_ctrl.creg_stats.stat;
|
|
if ((cmd->status & CREG_STAT_STATUS_MASK) == 0) {
|
|
dev_err(CARD_TO_DEV(card),
|
|
"Invalid status on creg command\n");
|
|
/*
|
|
* At this point we're probably reading garbage from HW. Don't
|
|
* do anything else that could mess up the system and let
|
|
* the sync function return an error.
|
|
*/
|
|
st = -EIO;
|
|
goto creg_done;
|
|
} else if (cmd->status & CREG_STAT_ERROR) {
|
|
st = -EIO;
|
|
}
|
|
|
|
if ((cmd->op == CREG_OP_READ)) {
|
|
unsigned int cnt8 = ioread32(card->regmap + CREG_CNT);
|
|
|
|
/* Paranoid Sanity Checks */
|
|
if (!cmd->buf) {
|
|
dev_err(CARD_TO_DEV(card),
|
|
"Buffer not given for read.\n");
|
|
st = -EIO;
|
|
goto creg_done;
|
|
}
|
|
if (cnt8 != cmd->cnt8) {
|
|
dev_err(CARD_TO_DEV(card),
|
|
"count mismatch\n");
|
|
st = -EIO;
|
|
goto creg_done;
|
|
}
|
|
|
|
st = copy_from_creg_data(card, cnt8, cmd->buf, cmd->stream);
|
|
}
|
|
|
|
creg_done:
|
|
if (cmd->cb)
|
|
cmd->cb(card, cmd, st);
|
|
|
|
kmem_cache_free(creg_cmd_pool, cmd);
|
|
|
|
spin_lock_bh(&card->creg_ctrl.lock);
|
|
card->creg_ctrl.active = 0;
|
|
creg_kick_queue(card);
|
|
spin_unlock_bh(&card->creg_ctrl.lock);
|
|
}
|
|
|
|
static void creg_reset(struct rsxx_cardinfo *card)
|
|
{
|
|
struct creg_cmd *cmd = NULL;
|
|
struct creg_cmd *tmp;
|
|
unsigned long flags;
|
|
|
|
/*
|
|
* mutex_trylock is used here because if reset_lock is taken then a
|
|
* reset is already happening. So, we can just go ahead and return.
|
|
*/
|
|
if (!mutex_trylock(&card->creg_ctrl.reset_lock))
|
|
return;
|
|
|
|
card->creg_ctrl.reset = 1;
|
|
spin_lock_irqsave(&card->irq_lock, flags);
|
|
rsxx_disable_ier_and_isr(card, CR_INTR_CREG | CR_INTR_EVENT);
|
|
spin_unlock_irqrestore(&card->irq_lock, flags);
|
|
|
|
dev_warn(CARD_TO_DEV(card),
|
|
"Resetting creg interface for recovery\n");
|
|
|
|
/* Cancel outstanding commands */
|
|
spin_lock_bh(&card->creg_ctrl.lock);
|
|
list_for_each_entry_safe(cmd, tmp, &card->creg_ctrl.queue, list) {
|
|
list_del(&cmd->list);
|
|
card->creg_ctrl.q_depth--;
|
|
if (cmd->cb)
|
|
cmd->cb(card, cmd, -ECANCELED);
|
|
kmem_cache_free(creg_cmd_pool, cmd);
|
|
}
|
|
|
|
cmd = card->creg_ctrl.active_cmd;
|
|
card->creg_ctrl.active_cmd = NULL;
|
|
if (cmd) {
|
|
if (timer_pending(&card->creg_ctrl.cmd_timer))
|
|
del_timer_sync(&card->creg_ctrl.cmd_timer);
|
|
|
|
if (cmd->cb)
|
|
cmd->cb(card, cmd, -ECANCELED);
|
|
kmem_cache_free(creg_cmd_pool, cmd);
|
|
|
|
card->creg_ctrl.active = 0;
|
|
}
|
|
spin_unlock_bh(&card->creg_ctrl.lock);
|
|
|
|
card->creg_ctrl.reset = 0;
|
|
spin_lock_irqsave(&card->irq_lock, flags);
|
|
rsxx_enable_ier_and_isr(card, CR_INTR_CREG | CR_INTR_EVENT);
|
|
spin_unlock_irqrestore(&card->irq_lock, flags);
|
|
|
|
mutex_unlock(&card->creg_ctrl.reset_lock);
|
|
}
|
|
|
|
/* Used for synchronous accesses */
|
|
struct creg_completion {
|
|
struct completion *cmd_done;
|
|
int st;
|
|
u32 creg_status;
|
|
};
|
|
|
|
static void creg_cmd_done_cb(struct rsxx_cardinfo *card,
|
|
struct creg_cmd *cmd,
|
|
int st)
|
|
{
|
|
struct creg_completion *cmd_completion;
|
|
|
|
cmd_completion = cmd->cb_private;
|
|
BUG_ON(!cmd_completion);
|
|
|
|
cmd_completion->st = st;
|
|
cmd_completion->creg_status = cmd->status;
|
|
complete(cmd_completion->cmd_done);
|
|
}
|
|
|
|
static int __issue_creg_rw(struct rsxx_cardinfo *card,
|
|
unsigned int op,
|
|
unsigned int addr,
|
|
unsigned int cnt8,
|
|
void *buf,
|
|
int stream,
|
|
unsigned int *hw_stat)
|
|
{
|
|
DECLARE_COMPLETION_ONSTACK(cmd_done);
|
|
struct creg_completion completion;
|
|
unsigned long timeout;
|
|
int st;
|
|
|
|
completion.cmd_done = &cmd_done;
|
|
completion.st = 0;
|
|
completion.creg_status = 0;
|
|
|
|
st = creg_queue_cmd(card, op, addr, cnt8, buf, stream, creg_cmd_done_cb,
|
|
&completion);
|
|
if (st)
|
|
return st;
|
|
|
|
/*
|
|
* This timeout is necessary for unresponsive hardware. The additional
|
|
* 20 seconds to used to guarantee that each cregs requests has time to
|
|
* complete.
|
|
*/
|
|
timeout = msecs_to_jiffies(CREG_TIMEOUT_MSEC *
|
|
card->creg_ctrl.q_depth + 20000);
|
|
|
|
/*
|
|
* The creg interface is guaranteed to complete. It has a timeout
|
|
* mechanism that will kick in if hardware does not respond.
|
|
*/
|
|
st = wait_for_completion_timeout(completion.cmd_done, timeout);
|
|
if (st == 0) {
|
|
/*
|
|
* This is really bad, because the kernel timer did not
|
|
* expire and notify us of a timeout!
|
|
*/
|
|
dev_crit(CARD_TO_DEV(card),
|
|
"cregs timer failed\n");
|
|
creg_reset(card);
|
|
return -EIO;
|
|
}
|
|
|
|
*hw_stat = completion.creg_status;
|
|
|
|
if (completion.st) {
|
|
dev_warn(CARD_TO_DEV(card),
|
|
"creg command failed(%d x%08x)\n",
|
|
completion.st, addr);
|
|
return completion.st;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int issue_creg_rw(struct rsxx_cardinfo *card,
|
|
u32 addr,
|
|
unsigned int size8,
|
|
void *data,
|
|
int stream,
|
|
int read)
|
|
{
|
|
unsigned int hw_stat;
|
|
unsigned int xfer;
|
|
unsigned int op;
|
|
int st;
|
|
|
|
op = read ? CREG_OP_READ : CREG_OP_WRITE;
|
|
|
|
do {
|
|
xfer = min_t(unsigned int, size8, MAX_CREG_DATA8);
|
|
|
|
st = __issue_creg_rw(card, op, addr, xfer,
|
|
data, stream, &hw_stat);
|
|
if (st)
|
|
return st;
|
|
|
|
data = (char *)data + xfer;
|
|
addr += xfer;
|
|
size8 -= xfer;
|
|
} while (size8);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ---------------------------- Public API ---------------------------------- */
|
|
int rsxx_creg_write(struct rsxx_cardinfo *card,
|
|
u32 addr,
|
|
unsigned int size8,
|
|
void *data,
|
|
int byte_stream)
|
|
{
|
|
return issue_creg_rw(card, addr, size8, data, byte_stream, 0);
|
|
}
|
|
|
|
int rsxx_creg_read(struct rsxx_cardinfo *card,
|
|
u32 addr,
|
|
unsigned int size8,
|
|
void *data,
|
|
int byte_stream)
|
|
{
|
|
return issue_creg_rw(card, addr, size8, data, byte_stream, 1);
|
|
}
|
|
|
|
int rsxx_get_card_state(struct rsxx_cardinfo *card, unsigned int *state)
|
|
{
|
|
return rsxx_creg_read(card, CREG_ADD_CARD_STATE,
|
|
sizeof(*state), state, 0);
|
|
}
|
|
|
|
int rsxx_get_card_size8(struct rsxx_cardinfo *card, u64 *size8)
|
|
{
|
|
unsigned int size;
|
|
int st;
|
|
|
|
st = rsxx_creg_read(card, CREG_ADD_CARD_SIZE,
|
|
sizeof(size), &size, 0);
|
|
if (st)
|
|
return st;
|
|
|
|
*size8 = (u64)size * RSXX_HW_BLK_SIZE;
|
|
return 0;
|
|
}
|
|
|
|
int rsxx_get_num_targets(struct rsxx_cardinfo *card,
|
|
unsigned int *n_targets)
|
|
{
|
|
return rsxx_creg_read(card, CREG_ADD_NUM_TARGETS,
|
|
sizeof(*n_targets), n_targets, 0);
|
|
}
|
|
|
|
int rsxx_get_card_capabilities(struct rsxx_cardinfo *card,
|
|
u32 *capabilities)
|
|
{
|
|
return rsxx_creg_read(card, CREG_ADD_CAPABILITIES,
|
|
sizeof(*capabilities), capabilities, 0);
|
|
}
|
|
|
|
int rsxx_issue_card_cmd(struct rsxx_cardinfo *card, u32 cmd)
|
|
{
|
|
return rsxx_creg_write(card, CREG_ADD_CARD_CMD,
|
|
sizeof(cmd), &cmd, 0);
|
|
}
|
|
|
|
|
|
/*----------------- HW Log Functions -------------------*/
|
|
static void hw_log_msg(struct rsxx_cardinfo *card, const char *str, int len)
|
|
{
|
|
static char level;
|
|
|
|
/*
|
|
* New messages start with "<#>", where # is the log level. Messages
|
|
* that extend past the log buffer will use the previous level
|
|
*/
|
|
if ((len > 3) && (str[0] == '<') && (str[2] == '>')) {
|
|
level = str[1];
|
|
str += 3; /* Skip past the log level. */
|
|
len -= 3;
|
|
}
|
|
|
|
switch (level) {
|
|
case '0':
|
|
dev_emerg(CARD_TO_DEV(card), "HW: %.*s", len, str);
|
|
break;
|
|
case '1':
|
|
dev_alert(CARD_TO_DEV(card), "HW: %.*s", len, str);
|
|
break;
|
|
case '2':
|
|
dev_crit(CARD_TO_DEV(card), "HW: %.*s", len, str);
|
|
break;
|
|
case '3':
|
|
dev_err(CARD_TO_DEV(card), "HW: %.*s", len, str);
|
|
break;
|
|
case '4':
|
|
dev_warn(CARD_TO_DEV(card), "HW: %.*s", len, str);
|
|
break;
|
|
case '5':
|
|
dev_notice(CARD_TO_DEV(card), "HW: %.*s", len, str);
|
|
break;
|
|
case '6':
|
|
dev_info(CARD_TO_DEV(card), "HW: %.*s", len, str);
|
|
break;
|
|
case '7':
|
|
dev_dbg(CARD_TO_DEV(card), "HW: %.*s", len, str);
|
|
break;
|
|
default:
|
|
dev_info(CARD_TO_DEV(card), "HW: %.*s", len, str);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The substrncpy function copies the src string (which includes the
|
|
* terminating '\0' character), up to the count into the dest pointer.
|
|
* Returns the number of bytes copied to dest.
|
|
*/
|
|
static int substrncpy(char *dest, const char *src, int count)
|
|
{
|
|
int max_cnt = count;
|
|
|
|
while (count) {
|
|
count--;
|
|
*dest = *src;
|
|
if (*dest == '\0')
|
|
break;
|
|
src++;
|
|
dest++;
|
|
}
|
|
return max_cnt - count;
|
|
}
|
|
|
|
|
|
static void read_hw_log_done(struct rsxx_cardinfo *card,
|
|
struct creg_cmd *cmd,
|
|
int st)
|
|
{
|
|
char *buf;
|
|
char *log_str;
|
|
int cnt;
|
|
int len;
|
|
int off;
|
|
|
|
buf = cmd->buf;
|
|
off = 0;
|
|
|
|
/* Failed getting the log message */
|
|
if (st)
|
|
return;
|
|
|
|
while (off < cmd->cnt8) {
|
|
log_str = &card->log.buf[card->log.buf_len];
|
|
cnt = min(cmd->cnt8 - off, LOG_BUF_SIZE8 - card->log.buf_len);
|
|
len = substrncpy(log_str, &buf[off], cnt);
|
|
|
|
off += len;
|
|
card->log.buf_len += len;
|
|
|
|
/*
|
|
* Flush the log if we've hit the end of a message or if we've
|
|
* run out of buffer space.
|
|
*/
|
|
if ((log_str[len - 1] == '\0') ||
|
|
(card->log.buf_len == LOG_BUF_SIZE8)) {
|
|
if (card->log.buf_len != 1) /* Don't log blank lines. */
|
|
hw_log_msg(card, card->log.buf,
|
|
card->log.buf_len);
|
|
card->log.buf_len = 0;
|
|
}
|
|
|
|
}
|
|
|
|
if (cmd->status & CREG_STAT_LOG_PENDING)
|
|
rsxx_read_hw_log(card);
|
|
}
|
|
|
|
int rsxx_read_hw_log(struct rsxx_cardinfo *card)
|
|
{
|
|
int st;
|
|
|
|
st = creg_queue_cmd(card, CREG_OP_READ, CREG_ADD_LOG,
|
|
sizeof(card->log.tmp), card->log.tmp,
|
|
1, read_hw_log_done, NULL);
|
|
if (st)
|
|
dev_err(CARD_TO_DEV(card),
|
|
"Failed getting log text\n");
|
|
|
|
return st;
|
|
}
|
|
|
|
/*-------------- IOCTL REG Access ------------------*/
|
|
static int issue_reg_cmd(struct rsxx_cardinfo *card,
|
|
struct rsxx_reg_access *cmd,
|
|
int read)
|
|
{
|
|
unsigned int op = read ? CREG_OP_READ : CREG_OP_WRITE;
|
|
|
|
return __issue_creg_rw(card, op, cmd->addr, cmd->cnt, cmd->data,
|
|
cmd->stream, &cmd->stat);
|
|
}
|
|
|
|
int rsxx_reg_access(struct rsxx_cardinfo *card,
|
|
struct rsxx_reg_access __user *ucmd,
|
|
int read)
|
|
{
|
|
struct rsxx_reg_access cmd;
|
|
int st;
|
|
|
|
st = copy_from_user(&cmd, ucmd, sizeof(cmd));
|
|
if (st)
|
|
return -EFAULT;
|
|
|
|
if (cmd.cnt > RSXX_MAX_REG_CNT)
|
|
return -EFAULT;
|
|
|
|
st = issue_reg_cmd(card, &cmd, read);
|
|
if (st)
|
|
return st;
|
|
|
|
st = put_user(cmd.stat, &ucmd->stat);
|
|
if (st)
|
|
return -EFAULT;
|
|
|
|
if (read) {
|
|
st = copy_to_user(ucmd->data, cmd.data, cmd.cnt);
|
|
if (st)
|
|
return -EFAULT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void rsxx_eeh_save_issued_creg(struct rsxx_cardinfo *card)
|
|
{
|
|
struct creg_cmd *cmd = NULL;
|
|
|
|
cmd = card->creg_ctrl.active_cmd;
|
|
card->creg_ctrl.active_cmd = NULL;
|
|
|
|
if (cmd) {
|
|
del_timer_sync(&card->creg_ctrl.cmd_timer);
|
|
|
|
spin_lock_bh(&card->creg_ctrl.lock);
|
|
list_add(&cmd->list, &card->creg_ctrl.queue);
|
|
card->creg_ctrl.q_depth++;
|
|
card->creg_ctrl.active = 0;
|
|
spin_unlock_bh(&card->creg_ctrl.lock);
|
|
}
|
|
}
|
|
|
|
void rsxx_kick_creg_queue(struct rsxx_cardinfo *card)
|
|
{
|
|
spin_lock_bh(&card->creg_ctrl.lock);
|
|
if (!list_empty(&card->creg_ctrl.queue))
|
|
creg_kick_queue(card);
|
|
spin_unlock_bh(&card->creg_ctrl.lock);
|
|
}
|
|
|
|
/*------------ Initialization & Setup --------------*/
|
|
int rsxx_creg_setup(struct rsxx_cardinfo *card)
|
|
{
|
|
card->creg_ctrl.active_cmd = NULL;
|
|
|
|
INIT_WORK(&card->creg_ctrl.done_work, creg_cmd_done);
|
|
mutex_init(&card->creg_ctrl.reset_lock);
|
|
INIT_LIST_HEAD(&card->creg_ctrl.queue);
|
|
spin_lock_init(&card->creg_ctrl.lock);
|
|
setup_timer(&card->creg_ctrl.cmd_timer, creg_cmd_timed_out,
|
|
(unsigned long) card);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void rsxx_creg_destroy(struct rsxx_cardinfo *card)
|
|
{
|
|
struct creg_cmd *cmd;
|
|
struct creg_cmd *tmp;
|
|
int cnt = 0;
|
|
|
|
/* Cancel outstanding commands */
|
|
spin_lock_bh(&card->creg_ctrl.lock);
|
|
list_for_each_entry_safe(cmd, tmp, &card->creg_ctrl.queue, list) {
|
|
list_del(&cmd->list);
|
|
if (cmd->cb)
|
|
cmd->cb(card, cmd, -ECANCELED);
|
|
kmem_cache_free(creg_cmd_pool, cmd);
|
|
cnt++;
|
|
}
|
|
|
|
if (cnt)
|
|
dev_info(CARD_TO_DEV(card),
|
|
"Canceled %d queue creg commands\n", cnt);
|
|
|
|
cmd = card->creg_ctrl.active_cmd;
|
|
card->creg_ctrl.active_cmd = NULL;
|
|
if (cmd) {
|
|
if (timer_pending(&card->creg_ctrl.cmd_timer))
|
|
del_timer_sync(&card->creg_ctrl.cmd_timer);
|
|
|
|
if (cmd->cb)
|
|
cmd->cb(card, cmd, -ECANCELED);
|
|
dev_info(CARD_TO_DEV(card),
|
|
"Canceled active creg command\n");
|
|
kmem_cache_free(creg_cmd_pool, cmd);
|
|
}
|
|
spin_unlock_bh(&card->creg_ctrl.lock);
|
|
|
|
cancel_work_sync(&card->creg_ctrl.done_work);
|
|
}
|
|
|
|
|
|
int rsxx_creg_init(void)
|
|
{
|
|
creg_cmd_pool = KMEM_CACHE(creg_cmd, SLAB_HWCACHE_ALIGN);
|
|
if (!creg_cmd_pool)
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void rsxx_creg_cleanup(void)
|
|
{
|
|
kmem_cache_destroy(creg_cmd_pool);
|
|
}
|