blob: d46dd7428a30ab7462039108607ef63f0b56895a [file] [log] [blame]
/* uisqueue.c
*
* Copyright (C) 2010 - 2013 UNISYS CORPORATION
* All rights reserved.
*
* 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, GOOD TITLE or
* NON INFRINGEMENT. See the GNU General Public License for more
* details.
*/
/* @ALL_INSPECTED */
#include <linux/kernel.h>
#include <linux/module.h>
#include "uisutils.h"
/* this is shorter than using __FILE__ (full path name) in
* debug/info/error messages */
#define CURRENT_FILE_PC UISLIB_PC_uisqueue_c
#define __MYFILE__ "uisqueue.c"
#define CHECK_CACHE_ALIGN 0
/*****************************************************/
/* Exported functions */
/*****************************************************/
/*
* Routine Description:
* Tries to insert the prebuilt signal pointed to by pSignal into the nth
* Queue of the Channel pointed to by pChannel
*
* Parameters:
* pChannel: (IN) points to the IO Channel
* Queue: (IN) nth Queue of the IO Channel
* pSignal: (IN) pointer to the signal
*
* Assumptions:
* - pChannel, Queue and pSignal are valid.
* - If insertion fails due to a full queue, the caller will determine the
* retry policy (e.g. wait & try again, report an error, etc.).
*
* Return value:
* 1 if the insertion succeeds, 0 if the queue was full.
*/
unsigned char spar_signal_insert(struct channel_header __iomem *ch, u32 queue,
void *sig)
{
void __iomem *psignal;
unsigned int head, tail, nof;
struct signal_queue_header __iomem *pqhdr =
(struct signal_queue_header __iomem *)
((char __iomem *)ch + readq(&ch->ch_space_offset))
+ queue;
/* capture current head and tail */
head = readl(&pqhdr->head);
tail = readl(&pqhdr->tail);
/* queue is full if (head + 1) % n equals tail */
if (((head + 1) % readl(&pqhdr->max_slots)) == tail) {
nof = readq(&pqhdr->num_overflows) + 1;
writeq(nof, &pqhdr->num_overflows);
return 0;
}
/* increment the head index */
head = (head + 1) % readl(&pqhdr->max_slots);
/* copy signal to the head location from the area pointed to
* by pSignal
*/
psignal = (char __iomem *)pqhdr + readq(&pqhdr->sig_base_offset) +
(head * readl(&pqhdr->signal_size));
memcpy_toio(psignal, sig, readl(&pqhdr->signal_size));
mb(); /* channel synch */
writel(head, &pqhdr->head);
writeq(readq(&pqhdr->num_sent) + 1, &pqhdr->num_sent);
return 1;
}
EXPORT_SYMBOL_GPL(spar_signal_insert);
/*
* Routine Description:
* Removes one signal from Channel pChannel's nth Queue at the
* time of the call and copies it into the memory pointed to by
* pSignal.
*
* Parameters:
* pChannel: (IN) points to the IO Channel
* Queue: (IN) nth Queue of the IO Channel
* pSignal: (IN) pointer to where the signals are to be copied
*
* Assumptions:
* - pChannel and Queue are valid.
* - pSignal points to a memory area large enough to hold queue's SignalSize
*
* Return value:
* 1 if the removal succeeds, 0 if the queue was empty.
*/
unsigned char
spar_signal_remove(struct channel_header __iomem *ch, u32 queue, void *sig)
{
void __iomem *psource;
unsigned int head, tail;
struct signal_queue_header __iomem *pqhdr =
(struct signal_queue_header __iomem *)((char __iomem *)ch +
readq(&ch->ch_space_offset)) + queue;
/* capture current head and tail */
head = readl(&pqhdr->head);
tail = readl(&pqhdr->tail);
/* queue is empty if the head index equals the tail index */
if (head == tail) {
writeq(readq(&pqhdr->num_empty) + 1, &pqhdr->num_empty);
return 0;
}
/* advance past the 'empty' front slot */
tail = (tail + 1) % readl(&pqhdr->max_slots);
/* copy signal from tail location to the area pointed to by pSignal */
psource = (char __iomem *)pqhdr + readq(&pqhdr->sig_base_offset) +
(tail * readl(&pqhdr->signal_size));
memcpy_fromio(sig, psource, readl(&pqhdr->signal_size));
mb(); /* channel synch */
writel(tail, &pqhdr->tail);
writeq(readq(&pqhdr->num_received) + 1,
&pqhdr->num_received);
return 1;
}
EXPORT_SYMBOL_GPL(spar_signal_remove);
/*
* Routine Description:
* Removes all signals present in Channel pChannel's nth Queue at the
* time of the call and copies them into the memory pointed to by
* pSignal. Returns the # of signals copied as the value of the routine.
*
* Parameters:
* pChannel: (IN) points to the IO Channel
* Queue: (IN) nth Queue of the IO Channel
* pSignal: (IN) pointer to where the signals are to be copied
*
* Assumptions:
* - pChannel and Queue are valid.
* - pSignal points to a memory area large enough to hold Queue's MaxSignals
* # of signals, each of which is Queue's SignalSize.
*
* Return value:
* # of signals copied.
*/
unsigned int spar_signal_remove_all(struct channel_header *ch, u32 queue,
void *sig)
{
void *psource;
unsigned int head, tail, count = 0;
struct signal_queue_header *pqhdr =
(struct signal_queue_header *)((char *)ch +
ch->ch_space_offset) + queue;
/* capture current head and tail */
head = pqhdr->head;
tail = pqhdr->tail;
/* queue is empty if the head index equals the tail index */
if (head == tail)
return 0;
while (head != tail) {
/* advance past the 'empty' front slot */
tail = (tail + 1) % pqhdr->max_slots;
/* copy signal from tail location to the area pointed
* to by pSignal
*/
psource =
(char *)pqhdr + pqhdr->sig_base_offset +
(tail * pqhdr->signal_size);
memcpy((char *)sig + (pqhdr->signal_size * count),
psource, pqhdr->signal_size);
mb(); /* channel synch */
pqhdr->tail = tail;
count++;
pqhdr->num_received++;
}
return count;
}
/*
* Routine Description:
* Determine whether a signal queue is empty.
*
* Parameters:
* pChannel: (IN) points to the IO Channel
* Queue: (IN) nth Queue of the IO Channel
*
* Return value:
* 1 if the signal queue is empty, 0 otherwise.
*/
unsigned char spar_signalqueue_empty(struct channel_header __iomem *ch,
u32 queue)
{
struct signal_queue_header __iomem *pqhdr =
(struct signal_queue_header __iomem *)((char __iomem *)ch +
readq(&ch->ch_space_offset)) + queue;
return readl(&pqhdr->head) == readl(&pqhdr->tail);
}
EXPORT_SYMBOL_GPL(spar_signalqueue_empty);
unsigned long long
uisqueue_interlocked_or(unsigned long long __iomem *tgt,
unsigned long long set)
{
unsigned long long i;
unsigned long long j;
j = readq(tgt);
do {
i = j;
j = cmpxchg((__force unsigned long long *)tgt, i, i | set);
} while (i != j);
return j;
}
EXPORT_SYMBOL_GPL(uisqueue_interlocked_or);
unsigned long long
uisqueue_interlocked_and(unsigned long long __iomem *tgt,
unsigned long long set)
{
unsigned long long i;
unsigned long long j;
j = readq(tgt);
do {
i = j;
j = cmpxchg((__force unsigned long long *)tgt, i, i & set);
} while (i != j);
return j;
}
EXPORT_SYMBOL_GPL(uisqueue_interlocked_and);
static u8
do_locked_client_insert(struct uisqueue_info *queueinfo,
unsigned int whichqueue,
void *signal,
spinlock_t *lock,
u8 *channel_id)
{
unsigned long flags;
u8 rc = 0;
spin_lock_irqsave(lock, flags);
if (!spar_channel_client_acquire_os(queueinfo->chan, channel_id))
goto unlock;
if (spar_signal_insert(queueinfo->chan, whichqueue, signal)) {
queueinfo->packets_sent++;
rc = 1;
}
spar_channel_client_release_os(queueinfo->chan, channel_id);
unlock:
spin_unlock_irqrestore((spinlock_t *)lock, flags);
return rc;
}
int
uisqueue_put_cmdrsp_with_lock_client(struct uisqueue_info *queueinfo,
struct uiscmdrsp *cmdrsp,
unsigned int whichqueue,
void *insertlock,
unsigned char issue_irq_if_empty,
u64 irq_handle,
char oktowait, u8 *channel_id)
{
while (!do_locked_client_insert(queueinfo, whichqueue, cmdrsp,
(spinlock_t *)insertlock,
channel_id)) {
if (oktowait != OK_TO_WAIT)
return 0; /* failed to queue */
/* try again */
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(msecs_to_jiffies(10));
}
return 1;
}
EXPORT_SYMBOL_GPL(uisqueue_put_cmdrsp_with_lock_client);
/* uisqueue_get_cmdrsp gets the cmdrsp entry at the head of the queue
* returns NULL if queue is empty */
int
uisqueue_get_cmdrsp(struct uisqueue_info *queueinfo,
void *cmdrsp, unsigned int whichqueue)
{
if (!spar_signal_remove(queueinfo->chan, whichqueue, cmdrsp))
return 0;
queueinfo->packets_received++;
return 1; /* Success */
}
EXPORT_SYMBOL_GPL(uisqueue_get_cmdrsp);