forked from Minki/linux
e99e88a9d2
This converts all remaining cases of the old setup_timer() API into using timer_setup(), where the callback argument is the structure already holding the struct timer_list. These should have no behavioral changes, since they just change which pointer is passed into the callback with the same available pointers after conversion. It handles the following examples, in addition to some other variations. Casting from unsigned long: void my_callback(unsigned long data) { struct something *ptr = (struct something *)data; ... } ... setup_timer(&ptr->my_timer, my_callback, ptr); and forced object casts: void my_callback(struct something *ptr) { ... } ... setup_timer(&ptr->my_timer, my_callback, (unsigned long)ptr); become: void my_callback(struct timer_list *t) { struct something *ptr = from_timer(ptr, t, my_timer); ... } ... timer_setup(&ptr->my_timer, my_callback, 0); Direct function assignments: void my_callback(unsigned long data) { struct something *ptr = (struct something *)data; ... } ... ptr->my_timer.function = my_callback; have a temporary cast added, along with converting the args: void my_callback(struct timer_list *t) { struct something *ptr = from_timer(ptr, t, my_timer); ... } ... ptr->my_timer.function = (TIMER_FUNC_TYPE)my_callback; And finally, callbacks without a data assignment: void my_callback(unsigned long data) { ... } ... setup_timer(&ptr->my_timer, my_callback, 0); have their argument renamed to verify they're unused during conversion: void my_callback(struct timer_list *unused) { ... } ... timer_setup(&ptr->my_timer, my_callback, 0); The conversion is done with the following Coccinelle script: spatch --very-quiet --all-includes --include-headers \ -I ./arch/x86/include -I ./arch/x86/include/generated \ -I ./include -I ./arch/x86/include/uapi \ -I ./arch/x86/include/generated/uapi -I ./include/uapi \ -I ./include/generated/uapi --include ./include/linux/kconfig.h \ --dir . \ --cocci-file ~/src/data/timer_setup.cocci @fix_address_of@ expression e; @@ setup_timer( -&(e) +&e , ...) // Update any raw setup_timer() usages that have a NULL callback, but // would otherwise match change_timer_function_usage, since the latter // will update all function assignments done in the face of a NULL // function initialization in setup_timer(). @change_timer_function_usage_NULL@ expression _E; identifier _timer; type _cast_data; @@ ( -setup_timer(&_E->_timer, NULL, _E); +timer_setup(&_E->_timer, NULL, 0); | -setup_timer(&_E->_timer, NULL, (_cast_data)_E); +timer_setup(&_E->_timer, NULL, 0); | -setup_timer(&_E._timer, NULL, &_E); +timer_setup(&_E._timer, NULL, 0); | -setup_timer(&_E._timer, NULL, (_cast_data)&_E); +timer_setup(&_E._timer, NULL, 0); ) @change_timer_function_usage@ expression _E; identifier _timer; struct timer_list _stl; identifier _callback; type _cast_func, _cast_data; @@ ( -setup_timer(&_E->_timer, _callback, _E); +timer_setup(&_E->_timer, _callback, 0); | -setup_timer(&_E->_timer, &_callback, _E); +timer_setup(&_E->_timer, _callback, 0); | -setup_timer(&_E->_timer, _callback, (_cast_data)_E); +timer_setup(&_E->_timer, _callback, 0); | -setup_timer(&_E->_timer, &_callback, (_cast_data)_E); +timer_setup(&_E->_timer, _callback, 0); | -setup_timer(&_E->_timer, (_cast_func)_callback, _E); +timer_setup(&_E->_timer, _callback, 0); | -setup_timer(&_E->_timer, (_cast_func)&_callback, _E); +timer_setup(&_E->_timer, _callback, 0); | -setup_timer(&_E->_timer, (_cast_func)_callback, (_cast_data)_E); +timer_setup(&_E->_timer, _callback, 0); | -setup_timer(&_E->_timer, (_cast_func)&_callback, (_cast_data)_E); +timer_setup(&_E->_timer, _callback, 0); | -setup_timer(&_E._timer, _callback, (_cast_data)_E); +timer_setup(&_E._timer, _callback, 0); | -setup_timer(&_E._timer, _callback, (_cast_data)&_E); +timer_setup(&_E._timer, _callback, 0); | -setup_timer(&_E._timer, &_callback, (_cast_data)_E); +timer_setup(&_E._timer, _callback, 0); | -setup_timer(&_E._timer, &_callback, (_cast_data)&_E); +timer_setup(&_E._timer, _callback, 0); | -setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)_E); +timer_setup(&_E._timer, _callback, 0); | -setup_timer(&_E._timer, (_cast_func)_callback, (_cast_data)&_E); +timer_setup(&_E._timer, _callback, 0); | -setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)_E); +timer_setup(&_E._timer, _callback, 0); | -setup_timer(&_E._timer, (_cast_func)&_callback, (_cast_data)&_E); +timer_setup(&_E._timer, _callback, 0); | _E->_timer@_stl.function = _callback; | _E->_timer@_stl.function = &_callback; | _E->_timer@_stl.function = (_cast_func)_callback; | _E->_timer@_stl.function = (_cast_func)&_callback; | _E._timer@_stl.function = _callback; | _E._timer@_stl.function = &_callback; | _E._timer@_stl.function = (_cast_func)_callback; | _E._timer@_stl.function = (_cast_func)&_callback; ) // callback(unsigned long arg) @change_callback_handle_cast depends on change_timer_function_usage@ identifier change_timer_function_usage._callback; identifier change_timer_function_usage._timer; type _origtype; identifier _origarg; type _handletype; identifier _handle; @@ void _callback( -_origtype _origarg +struct timer_list *t ) { ( ... when != _origarg _handletype *_handle = -(_handletype *)_origarg; +from_timer(_handle, t, _timer); ... when != _origarg | ... when != _origarg _handletype *_handle = -(void *)_origarg; +from_timer(_handle, t, _timer); ... when != _origarg | ... when != _origarg _handletype *_handle; ... when != _handle _handle = -(_handletype *)_origarg; +from_timer(_handle, t, _timer); ... when != _origarg | ... when != _origarg _handletype *_handle; ... when != _handle _handle = -(void *)_origarg; +from_timer(_handle, t, _timer); ... when != _origarg ) } // callback(unsigned long arg) without existing variable @change_callback_handle_cast_no_arg depends on change_timer_function_usage && !change_callback_handle_cast@ identifier change_timer_function_usage._callback; identifier change_timer_function_usage._timer; type _origtype; identifier _origarg; type _handletype; @@ void _callback( -_origtype _origarg +struct timer_list *t ) { + _handletype *_origarg = from_timer(_origarg, t, _timer); + ... when != _origarg - (_handletype *)_origarg + _origarg ... when != _origarg } // Avoid already converted callbacks. @match_callback_converted depends on change_timer_function_usage && !change_callback_handle_cast && !change_callback_handle_cast_no_arg@ identifier change_timer_function_usage._callback; identifier t; @@ void _callback(struct timer_list *t) { ... } // callback(struct something *handle) @change_callback_handle_arg depends on change_timer_function_usage && !match_callback_converted && !change_callback_handle_cast && !change_callback_handle_cast_no_arg@ identifier change_timer_function_usage._callback; identifier change_timer_function_usage._timer; type _handletype; identifier _handle; @@ void _callback( -_handletype *_handle +struct timer_list *t ) { + _handletype *_handle = from_timer(_handle, t, _timer); ... } // If change_callback_handle_arg ran on an empty function, remove // the added handler. @unchange_callback_handle_arg depends on change_timer_function_usage && change_callback_handle_arg@ identifier change_timer_function_usage._callback; identifier change_timer_function_usage._timer; type _handletype; identifier _handle; identifier t; @@ void _callback(struct timer_list *t) { - _handletype *_handle = from_timer(_handle, t, _timer); } // We only want to refactor the setup_timer() data argument if we've found // the matching callback. This undoes changes in change_timer_function_usage. @unchange_timer_function_usage depends on change_timer_function_usage && !change_callback_handle_cast && !change_callback_handle_cast_no_arg && !change_callback_handle_arg@ expression change_timer_function_usage._E; identifier change_timer_function_usage._timer; identifier change_timer_function_usage._callback; type change_timer_function_usage._cast_data; @@ ( -timer_setup(&_E->_timer, _callback, 0); +setup_timer(&_E->_timer, _callback, (_cast_data)_E); | -timer_setup(&_E._timer, _callback, 0); +setup_timer(&_E._timer, _callback, (_cast_data)&_E); ) // If we fixed a callback from a .function assignment, fix the // assignment cast now. @change_timer_function_assignment depends on change_timer_function_usage && (change_callback_handle_cast || change_callback_handle_cast_no_arg || change_callback_handle_arg)@ expression change_timer_function_usage._E; identifier change_timer_function_usage._timer; identifier change_timer_function_usage._callback; type _cast_func; typedef TIMER_FUNC_TYPE; @@ ( _E->_timer.function = -_callback +(TIMER_FUNC_TYPE)_callback ; | _E->_timer.function = -&_callback +(TIMER_FUNC_TYPE)_callback ; | _E->_timer.function = -(_cast_func)_callback; +(TIMER_FUNC_TYPE)_callback ; | _E->_timer.function = -(_cast_func)&_callback +(TIMER_FUNC_TYPE)_callback ; | _E._timer.function = -_callback +(TIMER_FUNC_TYPE)_callback ; | _E._timer.function = -&_callback; +(TIMER_FUNC_TYPE)_callback ; | _E._timer.function = -(_cast_func)_callback +(TIMER_FUNC_TYPE)_callback ; | _E._timer.function = -(_cast_func)&_callback +(TIMER_FUNC_TYPE)_callback ; ) // Sometimes timer functions are called directly. Replace matched args. @change_timer_function_calls depends on change_timer_function_usage && (change_callback_handle_cast || change_callback_handle_cast_no_arg || change_callback_handle_arg)@ expression _E; identifier change_timer_function_usage._timer; identifier change_timer_function_usage._callback; type _cast_data; @@ _callback( ( -(_cast_data)_E +&_E->_timer | -(_cast_data)&_E +&_E._timer | -_E +&_E->_timer ) ) // If a timer has been configured without a data argument, it can be // converted without regard to the callback argument, since it is unused. @match_timer_function_unused_data@ expression _E; identifier _timer; identifier _callback; @@ ( -setup_timer(&_E->_timer, _callback, 0); +timer_setup(&_E->_timer, _callback, 0); | -setup_timer(&_E->_timer, _callback, 0L); +timer_setup(&_E->_timer, _callback, 0); | -setup_timer(&_E->_timer, _callback, 0UL); +timer_setup(&_E->_timer, _callback, 0); | -setup_timer(&_E._timer, _callback, 0); +timer_setup(&_E._timer, _callback, 0); | -setup_timer(&_E._timer, _callback, 0L); +timer_setup(&_E._timer, _callback, 0); | -setup_timer(&_E._timer, _callback, 0UL); +timer_setup(&_E._timer, _callback, 0); | -setup_timer(&_timer, _callback, 0); +timer_setup(&_timer, _callback, 0); | -setup_timer(&_timer, _callback, 0L); +timer_setup(&_timer, _callback, 0); | -setup_timer(&_timer, _callback, 0UL); +timer_setup(&_timer, _callback, 0); | -setup_timer(_timer, _callback, 0); +timer_setup(_timer, _callback, 0); | -setup_timer(_timer, _callback, 0L); +timer_setup(_timer, _callback, 0); | -setup_timer(_timer, _callback, 0UL); +timer_setup(_timer, _callback, 0); ) @change_callback_unused_data depends on match_timer_function_unused_data@ identifier match_timer_function_unused_data._callback; type _origtype; identifier _origarg; @@ void _callback( -_origtype _origarg +struct timer_list *unused ) { ... when != _origarg } Signed-off-by: Kees Cook <keescook@chromium.org>
1103 lines
27 KiB
C
1103 lines
27 KiB
C
/*
|
|
* Filename: dma.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/slab.h>
|
|
#include "rsxx_priv.h"
|
|
|
|
struct rsxx_dma {
|
|
struct list_head list;
|
|
u8 cmd;
|
|
unsigned int laddr; /* Logical address */
|
|
struct {
|
|
u32 off;
|
|
u32 cnt;
|
|
} sub_page;
|
|
dma_addr_t dma_addr;
|
|
struct page *page;
|
|
unsigned int pg_off; /* Page Offset */
|
|
rsxx_dma_cb cb;
|
|
void *cb_data;
|
|
};
|
|
|
|
/* This timeout is used to detect a stalled DMA channel */
|
|
#define DMA_ACTIVITY_TIMEOUT msecs_to_jiffies(10000)
|
|
|
|
struct hw_status {
|
|
u8 status;
|
|
u8 tag;
|
|
__le16 count;
|
|
__le32 _rsvd2;
|
|
__le64 _rsvd3;
|
|
} __packed;
|
|
|
|
enum rsxx_dma_status {
|
|
DMA_SW_ERR = 0x1,
|
|
DMA_HW_FAULT = 0x2,
|
|
DMA_CANCELLED = 0x4,
|
|
};
|
|
|
|
struct hw_cmd {
|
|
u8 command;
|
|
u8 tag;
|
|
u8 _rsvd;
|
|
u8 sub_page; /* Bit[0:2]: 512byte offset */
|
|
/* Bit[4:6]: 512byte count */
|
|
__le32 device_addr;
|
|
__le64 host_addr;
|
|
} __packed;
|
|
|
|
enum rsxx_hw_cmd {
|
|
HW_CMD_BLK_DISCARD = 0x70,
|
|
HW_CMD_BLK_WRITE = 0x80,
|
|
HW_CMD_BLK_READ = 0xC0,
|
|
HW_CMD_BLK_RECON_READ = 0xE0,
|
|
};
|
|
|
|
enum rsxx_hw_status {
|
|
HW_STATUS_CRC = 0x01,
|
|
HW_STATUS_HARD_ERR = 0x02,
|
|
HW_STATUS_SOFT_ERR = 0x04,
|
|
HW_STATUS_FAULT = 0x08,
|
|
};
|
|
|
|
static struct kmem_cache *rsxx_dma_pool;
|
|
|
|
struct dma_tracker {
|
|
int next_tag;
|
|
struct rsxx_dma *dma;
|
|
};
|
|
|
|
#define DMA_TRACKER_LIST_SIZE8 (sizeof(struct dma_tracker_list) + \
|
|
(sizeof(struct dma_tracker) * RSXX_MAX_OUTSTANDING_CMDS))
|
|
|
|
struct dma_tracker_list {
|
|
spinlock_t lock;
|
|
int head;
|
|
struct dma_tracker list[0];
|
|
};
|
|
|
|
|
|
/*----------------- Misc Utility Functions -------------------*/
|
|
static unsigned int rsxx_addr8_to_laddr(u64 addr8, struct rsxx_cardinfo *card)
|
|
{
|
|
unsigned long long tgt_addr8;
|
|
|
|
tgt_addr8 = ((addr8 >> card->_stripe.upper_shift) &
|
|
card->_stripe.upper_mask) |
|
|
((addr8) & card->_stripe.lower_mask);
|
|
do_div(tgt_addr8, RSXX_HW_BLK_SIZE);
|
|
return tgt_addr8;
|
|
}
|
|
|
|
static unsigned int rsxx_get_dma_tgt(struct rsxx_cardinfo *card, u64 addr8)
|
|
{
|
|
unsigned int tgt;
|
|
|
|
tgt = (addr8 >> card->_stripe.target_shift) & card->_stripe.target_mask;
|
|
|
|
return tgt;
|
|
}
|
|
|
|
void rsxx_dma_queue_reset(struct rsxx_cardinfo *card)
|
|
{
|
|
/* Reset all DMA Command/Status Queues */
|
|
iowrite32(DMA_QUEUE_RESET, card->regmap + RESET);
|
|
}
|
|
|
|
static unsigned int get_dma_size(struct rsxx_dma *dma)
|
|
{
|
|
if (dma->sub_page.cnt)
|
|
return dma->sub_page.cnt << 9;
|
|
else
|
|
return RSXX_HW_BLK_SIZE;
|
|
}
|
|
|
|
|
|
/*----------------- DMA Tracker -------------------*/
|
|
static void set_tracker_dma(struct dma_tracker_list *trackers,
|
|
int tag,
|
|
struct rsxx_dma *dma)
|
|
{
|
|
trackers->list[tag].dma = dma;
|
|
}
|
|
|
|
static struct rsxx_dma *get_tracker_dma(struct dma_tracker_list *trackers,
|
|
int tag)
|
|
{
|
|
return trackers->list[tag].dma;
|
|
}
|
|
|
|
static int pop_tracker(struct dma_tracker_list *trackers)
|
|
{
|
|
int tag;
|
|
|
|
spin_lock(&trackers->lock);
|
|
tag = trackers->head;
|
|
if (tag != -1) {
|
|
trackers->head = trackers->list[tag].next_tag;
|
|
trackers->list[tag].next_tag = -1;
|
|
}
|
|
spin_unlock(&trackers->lock);
|
|
|
|
return tag;
|
|
}
|
|
|
|
static void push_tracker(struct dma_tracker_list *trackers, int tag)
|
|
{
|
|
spin_lock(&trackers->lock);
|
|
trackers->list[tag].next_tag = trackers->head;
|
|
trackers->head = tag;
|
|
trackers->list[tag].dma = NULL;
|
|
spin_unlock(&trackers->lock);
|
|
}
|
|
|
|
|
|
/*----------------- Interrupt Coalescing -------------*/
|
|
/*
|
|
* Interrupt Coalescing Register Format:
|
|
* Interrupt Timer (64ns units) [15:0]
|
|
* Interrupt Count [24:16]
|
|
* Reserved [31:25]
|
|
*/
|
|
#define INTR_COAL_LATENCY_MASK (0x0000ffff)
|
|
|
|
#define INTR_COAL_COUNT_SHIFT 16
|
|
#define INTR_COAL_COUNT_BITS 9
|
|
#define INTR_COAL_COUNT_MASK (((1 << INTR_COAL_COUNT_BITS) - 1) << \
|
|
INTR_COAL_COUNT_SHIFT)
|
|
#define INTR_COAL_LATENCY_UNITS_NS 64
|
|
|
|
|
|
static u32 dma_intr_coal_val(u32 mode, u32 count, u32 latency)
|
|
{
|
|
u32 latency_units = latency / INTR_COAL_LATENCY_UNITS_NS;
|
|
|
|
if (mode == RSXX_INTR_COAL_DISABLED)
|
|
return 0;
|
|
|
|
return ((count << INTR_COAL_COUNT_SHIFT) & INTR_COAL_COUNT_MASK) |
|
|
(latency_units & INTR_COAL_LATENCY_MASK);
|
|
|
|
}
|
|
|
|
static void dma_intr_coal_auto_tune(struct rsxx_cardinfo *card)
|
|
{
|
|
int i;
|
|
u32 q_depth = 0;
|
|
u32 intr_coal;
|
|
|
|
if (card->config.data.intr_coal.mode != RSXX_INTR_COAL_AUTO_TUNE ||
|
|
unlikely(card->eeh_state))
|
|
return;
|
|
|
|
for (i = 0; i < card->n_targets; i++)
|
|
q_depth += atomic_read(&card->ctrl[i].stats.hw_q_depth);
|
|
|
|
intr_coal = dma_intr_coal_val(card->config.data.intr_coal.mode,
|
|
q_depth / 2,
|
|
card->config.data.intr_coal.latency);
|
|
iowrite32(intr_coal, card->regmap + INTR_COAL);
|
|
}
|
|
|
|
/*----------------- RSXX DMA Handling -------------------*/
|
|
static void rsxx_free_dma(struct rsxx_dma_ctrl *ctrl, struct rsxx_dma *dma)
|
|
{
|
|
if (dma->cmd != HW_CMD_BLK_DISCARD) {
|
|
if (!pci_dma_mapping_error(ctrl->card->dev, dma->dma_addr)) {
|
|
pci_unmap_page(ctrl->card->dev, dma->dma_addr,
|
|
get_dma_size(dma),
|
|
dma->cmd == HW_CMD_BLK_WRITE ?
|
|
PCI_DMA_TODEVICE :
|
|
PCI_DMA_FROMDEVICE);
|
|
}
|
|
}
|
|
|
|
kmem_cache_free(rsxx_dma_pool, dma);
|
|
}
|
|
|
|
static void rsxx_complete_dma(struct rsxx_dma_ctrl *ctrl,
|
|
struct rsxx_dma *dma,
|
|
unsigned int status)
|
|
{
|
|
if (status & DMA_SW_ERR)
|
|
ctrl->stats.dma_sw_err++;
|
|
if (status & DMA_HW_FAULT)
|
|
ctrl->stats.dma_hw_fault++;
|
|
if (status & DMA_CANCELLED)
|
|
ctrl->stats.dma_cancelled++;
|
|
|
|
if (dma->cb)
|
|
dma->cb(ctrl->card, dma->cb_data, status ? 1 : 0);
|
|
|
|
rsxx_free_dma(ctrl, dma);
|
|
}
|
|
|
|
int rsxx_cleanup_dma_queue(struct rsxx_dma_ctrl *ctrl,
|
|
struct list_head *q, unsigned int done)
|
|
{
|
|
struct rsxx_dma *dma;
|
|
struct rsxx_dma *tmp;
|
|
int cnt = 0;
|
|
|
|
list_for_each_entry_safe(dma, tmp, q, list) {
|
|
list_del(&dma->list);
|
|
if (done & COMPLETE_DMA)
|
|
rsxx_complete_dma(ctrl, dma, DMA_CANCELLED);
|
|
else
|
|
rsxx_free_dma(ctrl, dma);
|
|
cnt++;
|
|
}
|
|
|
|
return cnt;
|
|
}
|
|
|
|
static void rsxx_requeue_dma(struct rsxx_dma_ctrl *ctrl,
|
|
struct rsxx_dma *dma)
|
|
{
|
|
/*
|
|
* Requeued DMAs go to the front of the queue so they are issued
|
|
* first.
|
|
*/
|
|
spin_lock_bh(&ctrl->queue_lock);
|
|
ctrl->stats.sw_q_depth++;
|
|
list_add(&dma->list, &ctrl->queue);
|
|
spin_unlock_bh(&ctrl->queue_lock);
|
|
}
|
|
|
|
static void rsxx_handle_dma_error(struct rsxx_dma_ctrl *ctrl,
|
|
struct rsxx_dma *dma,
|
|
u8 hw_st)
|
|
{
|
|
unsigned int status = 0;
|
|
int requeue_cmd = 0;
|
|
|
|
dev_dbg(CARD_TO_DEV(ctrl->card),
|
|
"Handling DMA error(cmd x%02x, laddr x%08x st:x%02x)\n",
|
|
dma->cmd, dma->laddr, hw_st);
|
|
|
|
if (hw_st & HW_STATUS_CRC)
|
|
ctrl->stats.crc_errors++;
|
|
if (hw_st & HW_STATUS_HARD_ERR)
|
|
ctrl->stats.hard_errors++;
|
|
if (hw_st & HW_STATUS_SOFT_ERR)
|
|
ctrl->stats.soft_errors++;
|
|
|
|
switch (dma->cmd) {
|
|
case HW_CMD_BLK_READ:
|
|
if (hw_st & (HW_STATUS_CRC | HW_STATUS_HARD_ERR)) {
|
|
if (ctrl->card->scrub_hard) {
|
|
dma->cmd = HW_CMD_BLK_RECON_READ;
|
|
requeue_cmd = 1;
|
|
ctrl->stats.reads_retried++;
|
|
} else {
|
|
status |= DMA_HW_FAULT;
|
|
ctrl->stats.reads_failed++;
|
|
}
|
|
} else if (hw_st & HW_STATUS_FAULT) {
|
|
status |= DMA_HW_FAULT;
|
|
ctrl->stats.reads_failed++;
|
|
}
|
|
|
|
break;
|
|
case HW_CMD_BLK_RECON_READ:
|
|
if (hw_st & (HW_STATUS_CRC | HW_STATUS_HARD_ERR)) {
|
|
/* Data could not be reconstructed. */
|
|
status |= DMA_HW_FAULT;
|
|
ctrl->stats.reads_failed++;
|
|
}
|
|
|
|
break;
|
|
case HW_CMD_BLK_WRITE:
|
|
status |= DMA_HW_FAULT;
|
|
ctrl->stats.writes_failed++;
|
|
|
|
break;
|
|
case HW_CMD_BLK_DISCARD:
|
|
status |= DMA_HW_FAULT;
|
|
ctrl->stats.discards_failed++;
|
|
|
|
break;
|
|
default:
|
|
dev_err(CARD_TO_DEV(ctrl->card),
|
|
"Unknown command in DMA!(cmd: x%02x "
|
|
"laddr x%08x st: x%02x\n",
|
|
dma->cmd, dma->laddr, hw_st);
|
|
status |= DMA_SW_ERR;
|
|
|
|
break;
|
|
}
|
|
|
|
if (requeue_cmd)
|
|
rsxx_requeue_dma(ctrl, dma);
|
|
else
|
|
rsxx_complete_dma(ctrl, dma, status);
|
|
}
|
|
|
|
static void dma_engine_stalled(struct timer_list *t)
|
|
{
|
|
struct rsxx_dma_ctrl *ctrl = from_timer(ctrl, t, activity_timer);
|
|
int cnt;
|
|
|
|
if (atomic_read(&ctrl->stats.hw_q_depth) == 0 ||
|
|
unlikely(ctrl->card->eeh_state))
|
|
return;
|
|
|
|
if (ctrl->cmd.idx != ioread32(ctrl->regmap + SW_CMD_IDX)) {
|
|
/*
|
|
* The dma engine was stalled because the SW_CMD_IDX write
|
|
* was lost. Issue it again to recover.
|
|
*/
|
|
dev_warn(CARD_TO_DEV(ctrl->card),
|
|
"SW_CMD_IDX write was lost, re-writing...\n");
|
|
iowrite32(ctrl->cmd.idx, ctrl->regmap + SW_CMD_IDX);
|
|
mod_timer(&ctrl->activity_timer,
|
|
jiffies + DMA_ACTIVITY_TIMEOUT);
|
|
} else {
|
|
dev_warn(CARD_TO_DEV(ctrl->card),
|
|
"DMA channel %d has stalled, faulting interface.\n",
|
|
ctrl->id);
|
|
ctrl->card->dma_fault = 1;
|
|
|
|
/* Clean up the DMA queue */
|
|
spin_lock(&ctrl->queue_lock);
|
|
cnt = rsxx_cleanup_dma_queue(ctrl, &ctrl->queue, COMPLETE_DMA);
|
|
spin_unlock(&ctrl->queue_lock);
|
|
|
|
cnt += rsxx_dma_cancel(ctrl);
|
|
|
|
if (cnt)
|
|
dev_info(CARD_TO_DEV(ctrl->card),
|
|
"Freed %d queued DMAs on channel %d\n",
|
|
cnt, ctrl->id);
|
|
}
|
|
}
|
|
|
|
static void rsxx_issue_dmas(struct rsxx_dma_ctrl *ctrl)
|
|
{
|
|
struct rsxx_dma *dma;
|
|
int tag;
|
|
int cmds_pending = 0;
|
|
struct hw_cmd *hw_cmd_buf;
|
|
int dir;
|
|
|
|
hw_cmd_buf = ctrl->cmd.buf;
|
|
|
|
if (unlikely(ctrl->card->halt) ||
|
|
unlikely(ctrl->card->eeh_state))
|
|
return;
|
|
|
|
while (1) {
|
|
spin_lock_bh(&ctrl->queue_lock);
|
|
if (list_empty(&ctrl->queue)) {
|
|
spin_unlock_bh(&ctrl->queue_lock);
|
|
break;
|
|
}
|
|
spin_unlock_bh(&ctrl->queue_lock);
|
|
|
|
tag = pop_tracker(ctrl->trackers);
|
|
if (tag == -1)
|
|
break;
|
|
|
|
spin_lock_bh(&ctrl->queue_lock);
|
|
dma = list_entry(ctrl->queue.next, struct rsxx_dma, list);
|
|
list_del(&dma->list);
|
|
ctrl->stats.sw_q_depth--;
|
|
spin_unlock_bh(&ctrl->queue_lock);
|
|
|
|
/*
|
|
* This will catch any DMAs that slipped in right before the
|
|
* fault, but was queued after all the other DMAs were
|
|
* cancelled.
|
|
*/
|
|
if (unlikely(ctrl->card->dma_fault)) {
|
|
push_tracker(ctrl->trackers, tag);
|
|
rsxx_complete_dma(ctrl, dma, DMA_CANCELLED);
|
|
continue;
|
|
}
|
|
|
|
if (dma->cmd != HW_CMD_BLK_DISCARD) {
|
|
if (dma->cmd == HW_CMD_BLK_WRITE)
|
|
dir = PCI_DMA_TODEVICE;
|
|
else
|
|
dir = PCI_DMA_FROMDEVICE;
|
|
|
|
/*
|
|
* The function pci_map_page is placed here because we
|
|
* can only, by design, issue up to 255 commands to the
|
|
* hardware at one time per DMA channel. So the maximum
|
|
* amount of mapped memory would be 255 * 4 channels *
|
|
* 4096 Bytes which is less than 2GB, the limit of a x8
|
|
* Non-HWWD PCIe slot. This way the pci_map_page
|
|
* function should never fail because of a lack of
|
|
* mappable memory.
|
|
*/
|
|
dma->dma_addr = pci_map_page(ctrl->card->dev, dma->page,
|
|
dma->pg_off, dma->sub_page.cnt << 9, dir);
|
|
if (pci_dma_mapping_error(ctrl->card->dev, dma->dma_addr)) {
|
|
push_tracker(ctrl->trackers, tag);
|
|
rsxx_complete_dma(ctrl, dma, DMA_CANCELLED);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
set_tracker_dma(ctrl->trackers, tag, dma);
|
|
hw_cmd_buf[ctrl->cmd.idx].command = dma->cmd;
|
|
hw_cmd_buf[ctrl->cmd.idx].tag = tag;
|
|
hw_cmd_buf[ctrl->cmd.idx]._rsvd = 0;
|
|
hw_cmd_buf[ctrl->cmd.idx].sub_page =
|
|
((dma->sub_page.cnt & 0x7) << 4) |
|
|
(dma->sub_page.off & 0x7);
|
|
|
|
hw_cmd_buf[ctrl->cmd.idx].device_addr =
|
|
cpu_to_le32(dma->laddr);
|
|
|
|
hw_cmd_buf[ctrl->cmd.idx].host_addr =
|
|
cpu_to_le64(dma->dma_addr);
|
|
|
|
dev_dbg(CARD_TO_DEV(ctrl->card),
|
|
"Issue DMA%d(laddr %d tag %d) to idx %d\n",
|
|
ctrl->id, dma->laddr, tag, ctrl->cmd.idx);
|
|
|
|
ctrl->cmd.idx = (ctrl->cmd.idx + 1) & RSXX_CS_IDX_MASK;
|
|
cmds_pending++;
|
|
|
|
if (dma->cmd == HW_CMD_BLK_WRITE)
|
|
ctrl->stats.writes_issued++;
|
|
else if (dma->cmd == HW_CMD_BLK_DISCARD)
|
|
ctrl->stats.discards_issued++;
|
|
else
|
|
ctrl->stats.reads_issued++;
|
|
}
|
|
|
|
/* Let HW know we've queued commands. */
|
|
if (cmds_pending) {
|
|
atomic_add(cmds_pending, &ctrl->stats.hw_q_depth);
|
|
mod_timer(&ctrl->activity_timer,
|
|
jiffies + DMA_ACTIVITY_TIMEOUT);
|
|
|
|
if (unlikely(ctrl->card->eeh_state)) {
|
|
del_timer_sync(&ctrl->activity_timer);
|
|
return;
|
|
}
|
|
|
|
iowrite32(ctrl->cmd.idx, ctrl->regmap + SW_CMD_IDX);
|
|
}
|
|
}
|
|
|
|
static void rsxx_dma_done(struct rsxx_dma_ctrl *ctrl)
|
|
{
|
|
struct rsxx_dma *dma;
|
|
unsigned long flags;
|
|
u16 count;
|
|
u8 status;
|
|
u8 tag;
|
|
struct hw_status *hw_st_buf;
|
|
|
|
hw_st_buf = ctrl->status.buf;
|
|
|
|
if (unlikely(ctrl->card->halt) ||
|
|
unlikely(ctrl->card->dma_fault) ||
|
|
unlikely(ctrl->card->eeh_state))
|
|
return;
|
|
|
|
count = le16_to_cpu(hw_st_buf[ctrl->status.idx].count);
|
|
|
|
while (count == ctrl->e_cnt) {
|
|
/*
|
|
* The read memory-barrier is necessary to keep aggressive
|
|
* processors/optimizers (such as the PPC Apple G5) from
|
|
* reordering the following status-buffer tag & status read
|
|
* *before* the count read on subsequent iterations of the
|
|
* loop!
|
|
*/
|
|
rmb();
|
|
|
|
status = hw_st_buf[ctrl->status.idx].status;
|
|
tag = hw_st_buf[ctrl->status.idx].tag;
|
|
|
|
dma = get_tracker_dma(ctrl->trackers, tag);
|
|
if (dma == NULL) {
|
|
spin_lock_irqsave(&ctrl->card->irq_lock, flags);
|
|
rsxx_disable_ier(ctrl->card, CR_INTR_DMA_ALL);
|
|
spin_unlock_irqrestore(&ctrl->card->irq_lock, flags);
|
|
|
|
dev_err(CARD_TO_DEV(ctrl->card),
|
|
"No tracker for tag %d "
|
|
"(idx %d id %d)\n",
|
|
tag, ctrl->status.idx, ctrl->id);
|
|
return;
|
|
}
|
|
|
|
dev_dbg(CARD_TO_DEV(ctrl->card),
|
|
"Completing DMA%d"
|
|
"(laddr x%x tag %d st: x%x cnt: x%04x) from idx %d.\n",
|
|
ctrl->id, dma->laddr, tag, status, count,
|
|
ctrl->status.idx);
|
|
|
|
atomic_dec(&ctrl->stats.hw_q_depth);
|
|
|
|
mod_timer(&ctrl->activity_timer,
|
|
jiffies + DMA_ACTIVITY_TIMEOUT);
|
|
|
|
if (status)
|
|
rsxx_handle_dma_error(ctrl, dma, status);
|
|
else
|
|
rsxx_complete_dma(ctrl, dma, 0);
|
|
|
|
push_tracker(ctrl->trackers, tag);
|
|
|
|
ctrl->status.idx = (ctrl->status.idx + 1) &
|
|
RSXX_CS_IDX_MASK;
|
|
ctrl->e_cnt++;
|
|
|
|
count = le16_to_cpu(hw_st_buf[ctrl->status.idx].count);
|
|
}
|
|
|
|
dma_intr_coal_auto_tune(ctrl->card);
|
|
|
|
if (atomic_read(&ctrl->stats.hw_q_depth) == 0)
|
|
del_timer_sync(&ctrl->activity_timer);
|
|
|
|
spin_lock_irqsave(&ctrl->card->irq_lock, flags);
|
|
rsxx_enable_ier(ctrl->card, CR_INTR_DMA(ctrl->id));
|
|
spin_unlock_irqrestore(&ctrl->card->irq_lock, flags);
|
|
|
|
spin_lock_bh(&ctrl->queue_lock);
|
|
if (ctrl->stats.sw_q_depth)
|
|
queue_work(ctrl->issue_wq, &ctrl->issue_dma_work);
|
|
spin_unlock_bh(&ctrl->queue_lock);
|
|
}
|
|
|
|
static void rsxx_schedule_issue(struct work_struct *work)
|
|
{
|
|
struct rsxx_dma_ctrl *ctrl;
|
|
|
|
ctrl = container_of(work, struct rsxx_dma_ctrl, issue_dma_work);
|
|
|
|
mutex_lock(&ctrl->work_lock);
|
|
rsxx_issue_dmas(ctrl);
|
|
mutex_unlock(&ctrl->work_lock);
|
|
}
|
|
|
|
static void rsxx_schedule_done(struct work_struct *work)
|
|
{
|
|
struct rsxx_dma_ctrl *ctrl;
|
|
|
|
ctrl = container_of(work, struct rsxx_dma_ctrl, dma_done_work);
|
|
|
|
mutex_lock(&ctrl->work_lock);
|
|
rsxx_dma_done(ctrl);
|
|
mutex_unlock(&ctrl->work_lock);
|
|
}
|
|
|
|
static blk_status_t rsxx_queue_discard(struct rsxx_cardinfo *card,
|
|
struct list_head *q,
|
|
unsigned int laddr,
|
|
rsxx_dma_cb cb,
|
|
void *cb_data)
|
|
{
|
|
struct rsxx_dma *dma;
|
|
|
|
dma = kmem_cache_alloc(rsxx_dma_pool, GFP_KERNEL);
|
|
if (!dma)
|
|
return BLK_STS_RESOURCE;
|
|
|
|
dma->cmd = HW_CMD_BLK_DISCARD;
|
|
dma->laddr = laddr;
|
|
dma->dma_addr = 0;
|
|
dma->sub_page.off = 0;
|
|
dma->sub_page.cnt = 0;
|
|
dma->page = NULL;
|
|
dma->pg_off = 0;
|
|
dma->cb = cb;
|
|
dma->cb_data = cb_data;
|
|
|
|
dev_dbg(CARD_TO_DEV(card), "Queuing[D] laddr %x\n", dma->laddr);
|
|
|
|
list_add_tail(&dma->list, q);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static blk_status_t rsxx_queue_dma(struct rsxx_cardinfo *card,
|
|
struct list_head *q,
|
|
int dir,
|
|
unsigned int dma_off,
|
|
unsigned int dma_len,
|
|
unsigned int laddr,
|
|
struct page *page,
|
|
unsigned int pg_off,
|
|
rsxx_dma_cb cb,
|
|
void *cb_data)
|
|
{
|
|
struct rsxx_dma *dma;
|
|
|
|
dma = kmem_cache_alloc(rsxx_dma_pool, GFP_KERNEL);
|
|
if (!dma)
|
|
return BLK_STS_RESOURCE;
|
|
|
|
dma->cmd = dir ? HW_CMD_BLK_WRITE : HW_CMD_BLK_READ;
|
|
dma->laddr = laddr;
|
|
dma->sub_page.off = (dma_off >> 9);
|
|
dma->sub_page.cnt = (dma_len >> 9);
|
|
dma->page = page;
|
|
dma->pg_off = pg_off;
|
|
dma->cb = cb;
|
|
dma->cb_data = cb_data;
|
|
|
|
dev_dbg(CARD_TO_DEV(card),
|
|
"Queuing[%c] laddr %x off %d cnt %d page %p pg_off %d\n",
|
|
dir ? 'W' : 'R', dma->laddr, dma->sub_page.off,
|
|
dma->sub_page.cnt, dma->page, dma->pg_off);
|
|
|
|
/* Queue the DMA */
|
|
list_add_tail(&dma->list, q);
|
|
|
|
return 0;
|
|
}
|
|
|
|
blk_status_t rsxx_dma_queue_bio(struct rsxx_cardinfo *card,
|
|
struct bio *bio,
|
|
atomic_t *n_dmas,
|
|
rsxx_dma_cb cb,
|
|
void *cb_data)
|
|
{
|
|
struct list_head dma_list[RSXX_MAX_TARGETS];
|
|
struct bio_vec bvec;
|
|
struct bvec_iter iter;
|
|
unsigned long long addr8;
|
|
unsigned int laddr;
|
|
unsigned int bv_len;
|
|
unsigned int bv_off;
|
|
unsigned int dma_off;
|
|
unsigned int dma_len;
|
|
int dma_cnt[RSXX_MAX_TARGETS];
|
|
int tgt;
|
|
blk_status_t st;
|
|
int i;
|
|
|
|
addr8 = bio->bi_iter.bi_sector << 9; /* sectors are 512 bytes */
|
|
atomic_set(n_dmas, 0);
|
|
|
|
for (i = 0; i < card->n_targets; i++) {
|
|
INIT_LIST_HEAD(&dma_list[i]);
|
|
dma_cnt[i] = 0;
|
|
}
|
|
|
|
if (bio_op(bio) == REQ_OP_DISCARD) {
|
|
bv_len = bio->bi_iter.bi_size;
|
|
|
|
while (bv_len > 0) {
|
|
tgt = rsxx_get_dma_tgt(card, addr8);
|
|
laddr = rsxx_addr8_to_laddr(addr8, card);
|
|
|
|
st = rsxx_queue_discard(card, &dma_list[tgt], laddr,
|
|
cb, cb_data);
|
|
if (st)
|
|
goto bvec_err;
|
|
|
|
dma_cnt[tgt]++;
|
|
atomic_inc(n_dmas);
|
|
addr8 += RSXX_HW_BLK_SIZE;
|
|
bv_len -= RSXX_HW_BLK_SIZE;
|
|
}
|
|
} else {
|
|
bio_for_each_segment(bvec, bio, iter) {
|
|
bv_len = bvec.bv_len;
|
|
bv_off = bvec.bv_offset;
|
|
|
|
while (bv_len > 0) {
|
|
tgt = rsxx_get_dma_tgt(card, addr8);
|
|
laddr = rsxx_addr8_to_laddr(addr8, card);
|
|
dma_off = addr8 & RSXX_HW_BLK_MASK;
|
|
dma_len = min(bv_len,
|
|
RSXX_HW_BLK_SIZE - dma_off);
|
|
|
|
st = rsxx_queue_dma(card, &dma_list[tgt],
|
|
bio_data_dir(bio),
|
|
dma_off, dma_len,
|
|
laddr, bvec.bv_page,
|
|
bv_off, cb, cb_data);
|
|
if (st)
|
|
goto bvec_err;
|
|
|
|
dma_cnt[tgt]++;
|
|
atomic_inc(n_dmas);
|
|
addr8 += dma_len;
|
|
bv_off += dma_len;
|
|
bv_len -= dma_len;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < card->n_targets; i++) {
|
|
if (!list_empty(&dma_list[i])) {
|
|
spin_lock_bh(&card->ctrl[i].queue_lock);
|
|
card->ctrl[i].stats.sw_q_depth += dma_cnt[i];
|
|
list_splice_tail(&dma_list[i], &card->ctrl[i].queue);
|
|
spin_unlock_bh(&card->ctrl[i].queue_lock);
|
|
|
|
queue_work(card->ctrl[i].issue_wq,
|
|
&card->ctrl[i].issue_dma_work);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
bvec_err:
|
|
for (i = 0; i < card->n_targets; i++)
|
|
rsxx_cleanup_dma_queue(&card->ctrl[i], &dma_list[i],
|
|
FREE_DMA);
|
|
return st;
|
|
}
|
|
|
|
|
|
/*----------------- DMA Engine Initialization & Setup -------------------*/
|
|
int rsxx_hw_buffers_init(struct pci_dev *dev, struct rsxx_dma_ctrl *ctrl)
|
|
{
|
|
ctrl->status.buf = pci_alloc_consistent(dev, STATUS_BUFFER_SIZE8,
|
|
&ctrl->status.dma_addr);
|
|
ctrl->cmd.buf = pci_alloc_consistent(dev, COMMAND_BUFFER_SIZE8,
|
|
&ctrl->cmd.dma_addr);
|
|
if (ctrl->status.buf == NULL || ctrl->cmd.buf == NULL)
|
|
return -ENOMEM;
|
|
|
|
memset(ctrl->status.buf, 0xac, STATUS_BUFFER_SIZE8);
|
|
iowrite32(lower_32_bits(ctrl->status.dma_addr),
|
|
ctrl->regmap + SB_ADD_LO);
|
|
iowrite32(upper_32_bits(ctrl->status.dma_addr),
|
|
ctrl->regmap + SB_ADD_HI);
|
|
|
|
memset(ctrl->cmd.buf, 0x83, COMMAND_BUFFER_SIZE8);
|
|
iowrite32(lower_32_bits(ctrl->cmd.dma_addr), ctrl->regmap + CB_ADD_LO);
|
|
iowrite32(upper_32_bits(ctrl->cmd.dma_addr), ctrl->regmap + CB_ADD_HI);
|
|
|
|
ctrl->status.idx = ioread32(ctrl->regmap + HW_STATUS_CNT);
|
|
if (ctrl->status.idx > RSXX_MAX_OUTSTANDING_CMDS) {
|
|
dev_crit(&dev->dev, "Failed reading status cnt x%x\n",
|
|
ctrl->status.idx);
|
|
return -EINVAL;
|
|
}
|
|
iowrite32(ctrl->status.idx, ctrl->regmap + HW_STATUS_CNT);
|
|
iowrite32(ctrl->status.idx, ctrl->regmap + SW_STATUS_CNT);
|
|
|
|
ctrl->cmd.idx = ioread32(ctrl->regmap + HW_CMD_IDX);
|
|
if (ctrl->cmd.idx > RSXX_MAX_OUTSTANDING_CMDS) {
|
|
dev_crit(&dev->dev, "Failed reading cmd cnt x%x\n",
|
|
ctrl->status.idx);
|
|
return -EINVAL;
|
|
}
|
|
iowrite32(ctrl->cmd.idx, ctrl->regmap + HW_CMD_IDX);
|
|
iowrite32(ctrl->cmd.idx, ctrl->regmap + SW_CMD_IDX);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rsxx_dma_ctrl_init(struct pci_dev *dev,
|
|
struct rsxx_dma_ctrl *ctrl)
|
|
{
|
|
int i;
|
|
int st;
|
|
|
|
memset(&ctrl->stats, 0, sizeof(ctrl->stats));
|
|
|
|
ctrl->trackers = vmalloc(DMA_TRACKER_LIST_SIZE8);
|
|
if (!ctrl->trackers)
|
|
return -ENOMEM;
|
|
|
|
ctrl->trackers->head = 0;
|
|
for (i = 0; i < RSXX_MAX_OUTSTANDING_CMDS; i++) {
|
|
ctrl->trackers->list[i].next_tag = i + 1;
|
|
ctrl->trackers->list[i].dma = NULL;
|
|
}
|
|
ctrl->trackers->list[RSXX_MAX_OUTSTANDING_CMDS-1].next_tag = -1;
|
|
spin_lock_init(&ctrl->trackers->lock);
|
|
|
|
spin_lock_init(&ctrl->queue_lock);
|
|
mutex_init(&ctrl->work_lock);
|
|
INIT_LIST_HEAD(&ctrl->queue);
|
|
|
|
timer_setup(&ctrl->activity_timer, dma_engine_stalled, 0);
|
|
|
|
ctrl->issue_wq = alloc_ordered_workqueue(DRIVER_NAME"_issue", 0);
|
|
if (!ctrl->issue_wq)
|
|
return -ENOMEM;
|
|
|
|
ctrl->done_wq = alloc_ordered_workqueue(DRIVER_NAME"_done", 0);
|
|
if (!ctrl->done_wq)
|
|
return -ENOMEM;
|
|
|
|
INIT_WORK(&ctrl->issue_dma_work, rsxx_schedule_issue);
|
|
INIT_WORK(&ctrl->dma_done_work, rsxx_schedule_done);
|
|
|
|
st = rsxx_hw_buffers_init(dev, ctrl);
|
|
if (st)
|
|
return st;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rsxx_dma_stripe_setup(struct rsxx_cardinfo *card,
|
|
unsigned int stripe_size8)
|
|
{
|
|
if (!is_power_of_2(stripe_size8)) {
|
|
dev_err(CARD_TO_DEV(card),
|
|
"stripe_size is NOT a power of 2!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
card->_stripe.lower_mask = stripe_size8 - 1;
|
|
|
|
card->_stripe.upper_mask = ~(card->_stripe.lower_mask);
|
|
card->_stripe.upper_shift = ffs(card->n_targets) - 1;
|
|
|
|
card->_stripe.target_mask = card->n_targets - 1;
|
|
card->_stripe.target_shift = ffs(stripe_size8) - 1;
|
|
|
|
dev_dbg(CARD_TO_DEV(card), "_stripe.lower_mask = x%016llx\n",
|
|
card->_stripe.lower_mask);
|
|
dev_dbg(CARD_TO_DEV(card), "_stripe.upper_shift = x%016llx\n",
|
|
card->_stripe.upper_shift);
|
|
dev_dbg(CARD_TO_DEV(card), "_stripe.upper_mask = x%016llx\n",
|
|
card->_stripe.upper_mask);
|
|
dev_dbg(CARD_TO_DEV(card), "_stripe.target_mask = x%016llx\n",
|
|
card->_stripe.target_mask);
|
|
dev_dbg(CARD_TO_DEV(card), "_stripe.target_shift = x%016llx\n",
|
|
card->_stripe.target_shift);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int rsxx_dma_configure(struct rsxx_cardinfo *card)
|
|
{
|
|
u32 intr_coal;
|
|
|
|
intr_coal = dma_intr_coal_val(card->config.data.intr_coal.mode,
|
|
card->config.data.intr_coal.count,
|
|
card->config.data.intr_coal.latency);
|
|
iowrite32(intr_coal, card->regmap + INTR_COAL);
|
|
|
|
return rsxx_dma_stripe_setup(card, card->config.data.stripe_size);
|
|
}
|
|
|
|
int rsxx_dma_setup(struct rsxx_cardinfo *card)
|
|
{
|
|
unsigned long flags;
|
|
int st;
|
|
int i;
|
|
|
|
dev_info(CARD_TO_DEV(card),
|
|
"Initializing %d DMA targets\n",
|
|
card->n_targets);
|
|
|
|
/* Regmap is divided up into 4K chunks. One for each DMA channel */
|
|
for (i = 0; i < card->n_targets; i++)
|
|
card->ctrl[i].regmap = card->regmap + (i * 4096);
|
|
|
|
card->dma_fault = 0;
|
|
|
|
/* Reset the DMA queues */
|
|
rsxx_dma_queue_reset(card);
|
|
|
|
/************* Setup DMA Control *************/
|
|
for (i = 0; i < card->n_targets; i++) {
|
|
st = rsxx_dma_ctrl_init(card->dev, &card->ctrl[i]);
|
|
if (st)
|
|
goto failed_dma_setup;
|
|
|
|
card->ctrl[i].card = card;
|
|
card->ctrl[i].id = i;
|
|
}
|
|
|
|
card->scrub_hard = 1;
|
|
|
|
if (card->config_valid)
|
|
rsxx_dma_configure(card);
|
|
|
|
/* Enable the interrupts after all setup has completed. */
|
|
for (i = 0; i < card->n_targets; i++) {
|
|
spin_lock_irqsave(&card->irq_lock, flags);
|
|
rsxx_enable_ier_and_isr(card, CR_INTR_DMA(i));
|
|
spin_unlock_irqrestore(&card->irq_lock, flags);
|
|
}
|
|
|
|
return 0;
|
|
|
|
failed_dma_setup:
|
|
for (i = 0; i < card->n_targets; i++) {
|
|
struct rsxx_dma_ctrl *ctrl = &card->ctrl[i];
|
|
|
|
if (ctrl->issue_wq) {
|
|
destroy_workqueue(ctrl->issue_wq);
|
|
ctrl->issue_wq = NULL;
|
|
}
|
|
|
|
if (ctrl->done_wq) {
|
|
destroy_workqueue(ctrl->done_wq);
|
|
ctrl->done_wq = NULL;
|
|
}
|
|
|
|
if (ctrl->trackers)
|
|
vfree(ctrl->trackers);
|
|
|
|
if (ctrl->status.buf)
|
|
pci_free_consistent(card->dev, STATUS_BUFFER_SIZE8,
|
|
ctrl->status.buf,
|
|
ctrl->status.dma_addr);
|
|
if (ctrl->cmd.buf)
|
|
pci_free_consistent(card->dev, COMMAND_BUFFER_SIZE8,
|
|
ctrl->cmd.buf, ctrl->cmd.dma_addr);
|
|
}
|
|
|
|
return st;
|
|
}
|
|
|
|
int rsxx_dma_cancel(struct rsxx_dma_ctrl *ctrl)
|
|
{
|
|
struct rsxx_dma *dma;
|
|
int i;
|
|
int cnt = 0;
|
|
|
|
/* Clean up issued DMAs */
|
|
for (i = 0; i < RSXX_MAX_OUTSTANDING_CMDS; i++) {
|
|
dma = get_tracker_dma(ctrl->trackers, i);
|
|
if (dma) {
|
|
atomic_dec(&ctrl->stats.hw_q_depth);
|
|
rsxx_complete_dma(ctrl, dma, DMA_CANCELLED);
|
|
push_tracker(ctrl->trackers, i);
|
|
cnt++;
|
|
}
|
|
}
|
|
|
|
return cnt;
|
|
}
|
|
|
|
void rsxx_dma_destroy(struct rsxx_cardinfo *card)
|
|
{
|
|
struct rsxx_dma_ctrl *ctrl;
|
|
int i;
|
|
|
|
for (i = 0; i < card->n_targets; i++) {
|
|
ctrl = &card->ctrl[i];
|
|
|
|
if (ctrl->issue_wq) {
|
|
destroy_workqueue(ctrl->issue_wq);
|
|
ctrl->issue_wq = NULL;
|
|
}
|
|
|
|
if (ctrl->done_wq) {
|
|
destroy_workqueue(ctrl->done_wq);
|
|
ctrl->done_wq = NULL;
|
|
}
|
|
|
|
if (timer_pending(&ctrl->activity_timer))
|
|
del_timer_sync(&ctrl->activity_timer);
|
|
|
|
/* Clean up the DMA queue */
|
|
spin_lock_bh(&ctrl->queue_lock);
|
|
rsxx_cleanup_dma_queue(ctrl, &ctrl->queue, COMPLETE_DMA);
|
|
spin_unlock_bh(&ctrl->queue_lock);
|
|
|
|
rsxx_dma_cancel(ctrl);
|
|
|
|
vfree(ctrl->trackers);
|
|
|
|
pci_free_consistent(card->dev, STATUS_BUFFER_SIZE8,
|
|
ctrl->status.buf, ctrl->status.dma_addr);
|
|
pci_free_consistent(card->dev, COMMAND_BUFFER_SIZE8,
|
|
ctrl->cmd.buf, ctrl->cmd.dma_addr);
|
|
}
|
|
}
|
|
|
|
int rsxx_eeh_save_issued_dmas(struct rsxx_cardinfo *card)
|
|
{
|
|
int i;
|
|
int j;
|
|
int cnt;
|
|
struct rsxx_dma *dma;
|
|
struct list_head *issued_dmas;
|
|
|
|
issued_dmas = kzalloc(sizeof(*issued_dmas) * card->n_targets,
|
|
GFP_KERNEL);
|
|
if (!issued_dmas)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < card->n_targets; i++) {
|
|
INIT_LIST_HEAD(&issued_dmas[i]);
|
|
cnt = 0;
|
|
for (j = 0; j < RSXX_MAX_OUTSTANDING_CMDS; j++) {
|
|
dma = get_tracker_dma(card->ctrl[i].trackers, j);
|
|
if (dma == NULL)
|
|
continue;
|
|
|
|
if (dma->cmd == HW_CMD_BLK_WRITE)
|
|
card->ctrl[i].stats.writes_issued--;
|
|
else if (dma->cmd == HW_CMD_BLK_DISCARD)
|
|
card->ctrl[i].stats.discards_issued--;
|
|
else
|
|
card->ctrl[i].stats.reads_issued--;
|
|
|
|
if (dma->cmd != HW_CMD_BLK_DISCARD) {
|
|
pci_unmap_page(card->dev, dma->dma_addr,
|
|
get_dma_size(dma),
|
|
dma->cmd == HW_CMD_BLK_WRITE ?
|
|
PCI_DMA_TODEVICE :
|
|
PCI_DMA_FROMDEVICE);
|
|
}
|
|
|
|
list_add_tail(&dma->list, &issued_dmas[i]);
|
|
push_tracker(card->ctrl[i].trackers, j);
|
|
cnt++;
|
|
}
|
|
|
|
spin_lock_bh(&card->ctrl[i].queue_lock);
|
|
list_splice(&issued_dmas[i], &card->ctrl[i].queue);
|
|
|
|
atomic_sub(cnt, &card->ctrl[i].stats.hw_q_depth);
|
|
card->ctrl[i].stats.sw_q_depth += cnt;
|
|
card->ctrl[i].e_cnt = 0;
|
|
spin_unlock_bh(&card->ctrl[i].queue_lock);
|
|
}
|
|
|
|
kfree(issued_dmas);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int rsxx_dma_init(void)
|
|
{
|
|
rsxx_dma_pool = KMEM_CACHE(rsxx_dma, SLAB_HWCACHE_ALIGN);
|
|
if (!rsxx_dma_pool)
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void rsxx_dma_cleanup(void)
|
|
{
|
|
kmem_cache_destroy(rsxx_dma_pool);
|
|
}
|
|
|