#include "pvrusb2-io.h"
#include "pvrusb2-debug.h"
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/mutex.h>
static const char *pvr2_buffer_state_decode(enum pvr2_buffer_state);
#define BUFFER_SIG 0x47653271
#ifdef SANITY_CHECK_BUFFERS
#define BUFFER_CHECK(bp) do { \
if ((bp)->signature != BUFFER_SIG) { \
pvr2_trace(PVR2_TRACE_ERROR_LEGS, \
"Buffer %p is bad at %s:%d", \
(bp), __FILE__, __LINE__); \
pvr2_buffer_describe(bp, "BadSig"); \
BUG(); \
} \
} while (0)
#else
#define BUFFER_CHECK(bp) do {} while (0)
#endif
struct pvr2_stream {
struct list_head queued_list;
unsigned int q_count;
unsigned int q_bcount;
struct list_head ready_list;
unsigned int r_count;
unsigned int r_bcount;
struct list_head idle_list;
unsigned int i_count;
unsigned int i_bcount;
struct pvr2_buffer **buffers;
unsigned int buffer_slot_count;
unsigned int buffer_total_count;
unsigned int buffer_target_count;
pvr2_stream_callback callback_func;
void *callback_data;
struct usb_device *dev;
int endpoint;
spinlock_t list_lock;
struct mutex mutex;
unsigned int fail_count;
unsigned int fail_tolerance;
unsigned int buffers_processed;
unsigned int buffers_failed;
unsigned int bytes_processed;
};
struct pvr2_buffer {
int id;
int signature;
enum pvr2_buffer_state state;
void *ptr;
unsigned int max_count;
unsigned int used_count;
int status;
struct pvr2_stream *stream;
struct list_head list_overhead;
struct urb *purb;
};
static const char *pvr2_buffer_state_decode(enum pvr2_buffer_state st)
{
switch (st) {
case pvr2_buffer_state_none: return "none";
case pvr2_buffer_state_idle: return "idle";
case pvr2_buffer_state_queued: return "queued";
case pvr2_buffer_state_ready: return "ready";
}
return "unknown";
}
#ifdef SANITY_CHECK_BUFFERS
static void pvr2_buffer_describe(struct pvr2_buffer *bp, const char *msg)
{
pvr2_trace(PVR2_TRACE_INFO,
"buffer%s%s %p state=%s id=%d status=%d stream=%p purb=%p sig=0x%x",
(msg ? " " : ""),
(msg ? msg : ""),
bp,
(bp ? pvr2_buffer_state_decode(bp->state) : "(invalid)"),
(bp ? bp->id : 0),
(bp ? bp->status : 0),
(bp ? bp->stream : NULL),
(bp ? bp->purb : NULL),
(bp ? bp->signature : 0));
}
#endif /* SANITY_CHECK_BUFFERS */
static void pvr2_buffer_remove(struct pvr2_buffer *bp)
{
unsigned int *cnt;
unsigned int *bcnt;
unsigned int ccnt;
struct pvr2_stream *sp = bp->stream;
switch (bp->state) {
case pvr2_buffer_state_idle:
cnt = &sp->i_count;
bcnt = &sp->i_bcount;
ccnt = bp->max_count;
break;
case pvr2_buffer_state_queued:
cnt = &sp->q_count;
bcnt = &sp->q_bcount;
ccnt = bp->max_count;
break;
case pvr2_buffer_state_ready:
cnt = &sp->r_count;
bcnt = &sp->r_bcount;
ccnt = bp->used_count;
break;
default:
return;
}
list_del_init(&bp->list_overhead);
(*cnt)--;
(*bcnt) -= ccnt;
pvr2_trace(PVR2_TRACE_BUF_FLOW,
"/*---TRACE_FLOW---*/ bufferPool %8s dec cap=%07d cnt=%02d",
pvr2_buffer_state_decode(bp->state), *bcnt, *cnt);
bp->state = pvr2_buffer_state_none;
}
static void pvr2_buffer_set_none(struct pvr2_buffer *bp)
{
unsigned long irq_flags;
struct pvr2_stream *sp;
BUFFER_CHECK(bp);
sp = bp->stream;
pvr2_trace(PVR2_TRACE_BUF_FLOW,
"/*---TRACE_FLOW---*/ bufferState %p %6s --> %6s",
bp,
pvr2_buffer_state_decode(bp->state),
pvr2_buffer_state_decode(pvr2_buffer_state_none));
spin_lock_irqsave(&sp->list_lock, irq_flags);
pvr2_buffer_remove(bp);
spin_unlock_irqrestore(&sp->list_lock, irq_flags);
}
static int pvr2_buffer_set_ready(struct pvr2_buffer *bp)
{
int fl;
unsigned long irq_flags;
struct pvr2_stream *sp;
BUFFER_CHECK(bp);
sp = bp->stream;
pvr2_trace(PVR2_TRACE_BUF_FLOW,
"/*---TRACE_FLOW---*/ bufferState %p %6s --> %6s",
bp,
pvr2_buffer_state_decode(bp->state),
pvr2_buffer_state_decode(pvr2_buffer_state_ready));
spin_lock_irqsave(&sp->list_lock, irq_flags);
fl = (sp->r_count == 0);
pvr2_buffer_remove(bp);
list_add_tail(&bp->list_overhead, &sp->ready_list);
bp->state = pvr2_buffer_state_ready;
(sp->r_count)++;
sp->r_bcount += bp->used_count;
pvr2_trace(PVR2_TRACE_BUF_FLOW,
"/*---TRACE_FLOW---*/ bufferPool %8s inc cap=%07d cnt=%02d",
pvr2_buffer_state_decode(bp->state),
sp->r_bcount, sp->r_count);
spin_unlock_irqrestore(&sp->list_lock, irq_flags);
return fl;
}
static void pvr2_buffer_set_idle(struct pvr2_buffer *bp)
{
unsigned long irq_flags;
struct pvr2_stream *sp;
BUFFER_CHECK(bp);
sp = bp->stream;
pvr2_trace(PVR2_TRACE_BUF_FLOW,
"/*---TRACE_FLOW---*/ bufferState %p %6s --> %6s",
bp,
pvr2_buffer_state_decode(bp->state),
pvr2_buffer_state_decode(pvr2_buffer_state_idle));
spin_lock_irqsave(&sp->list_lock, irq_flags);
pvr2_buffer_remove(bp);
list_add_tail(&bp->list_overhead, &sp->idle_list);
bp->state = pvr2_buffer_state_idle;
(sp->i_count)++;
sp->i_bcount += bp->max_count;
pvr2_trace(PVR2_TRACE_BUF_FLOW,
"/*---TRACE_FLOW---*/ bufferPool %8s inc cap=%07d cnt=%02d",
pvr2_buffer_state_decode(bp->state),
sp->i_bcount, sp->i_count);
spin_unlock_irqrestore(&sp->list_lock, irq_flags);
}
static void pvr2_buffer_set_queued(struct pvr2_buffer *bp)
{
unsigned long irq_flags;
struct pvr2_stream *sp;
BUFFER_CHECK(bp);
sp = bp->stream;
pvr2_trace(PVR2_TRACE_BUF_FLOW,
"/*---TRACE_FLOW---*/ bufferState %p %6s --> %6s",
bp,
pvr2_buffer_state_decode(bp->state),
pvr2_buffer_state_decode(pvr2_buffer_state_queued));
spin_lock_irqsave(&sp->list_lock, irq_flags);
pvr2_buffer_remove(bp);
list_add_tail(&bp->list_overhead, &sp->queued_list);
bp->state = pvr2_buffer_state_queued;
(sp->q_count)++;
sp->q_bcount += bp->max_count;
pvr2_trace(PVR2_TRACE_BUF_FLOW,
"/*---TRACE_FLOW---*/ bufferPool %8s inc cap=%07d cnt=%02d",
pvr2_buffer_state_decode(bp->state),
sp->q_bcount, sp->q_count);
spin_unlock_irqrestore(&sp->list_lock, irq_flags);
}
static void pvr2_buffer_wipe(struct pvr2_buffer *bp)
{
if (bp->state == pvr2_buffer_state_queued) {
usb_kill_urb(bp->purb);
}
}
static int pvr2_buffer_init(struct pvr2_buffer *bp,
struct pvr2_stream *sp,
unsigned int id)
{
memset(bp, 0, sizeof(*bp));
bp->signature = BUFFER_SIG;
bp->id = id;
pvr2_trace(PVR2_TRACE_BUF_POOL,
"/*---TRACE_FLOW---*/ bufferInit %p stream=%p", bp, sp);
bp->stream = sp;
bp->state = pvr2_buffer_state_none;
INIT_LIST_HEAD(&bp->list_overhead);
bp->purb = usb_alloc_urb(0, GFP_KERNEL);
if (! bp->purb) return -ENOMEM;
#ifdef SANITY_CHECK_BUFFERS
pvr2_buffer_describe(bp, "create");
#endif
return 0;
}
static void pvr2_buffer_done(struct pvr2_buffer *bp)
{
#ifdef SANITY_CHECK_BUFFERS
pvr2_buffer_describe(bp, "delete");
#endif
pvr2_buffer_wipe(bp);
pvr2_buffer_set_none(bp);
bp->signature = 0;
bp->stream = NULL;
usb_free_urb(bp->purb);
pvr2_trace(PVR2_TRACE_BUF_POOL, "/*---TRACE_FLOW---*/ bufferDone %p",
bp);
}
static int pvr2_stream_buffer_count(struct pvr2_stream *sp, unsigned int cnt)
{
int ret;
unsigned int scnt;
if (cnt == sp->buffer_total_count) return 0;
pvr2_trace(PVR2_TRACE_BUF_POOL,
"/*---TRACE_FLOW---*/ poolResize stream=%p cur=%d adj=%+d",
sp,
sp->buffer_total_count,
cnt-sp->buffer_total_count);
scnt = cnt & ~0x1f;
if (cnt > scnt) scnt += 0x20;
if (cnt > sp->buffer_total_count) {
if (scnt > sp->buffer_slot_count) {
struct pvr2_buffer **nb;
nb = kmalloc_array(scnt, sizeof(*nb), GFP_KERNEL);
if (!nb) return -ENOMEM;
if (sp->buffer_slot_count) {
memcpy(nb, sp->buffers,
sp->buffer_slot_count * sizeof(*nb));
kfree(sp->buffers);
}
sp->buffers = nb;
sp->buffer_slot_count = scnt;
}
while (sp->buffer_total_count < cnt) {
struct pvr2_buffer *bp;
bp = kmalloc(sizeof(*bp), GFP_KERNEL);
if (!bp) return -ENOMEM;
ret = pvr2_buffer_init(bp, sp, sp->buffer_total_count);
if (ret) {
kfree(bp);
return -ENOMEM;
}
sp->buffers[sp->buffer_total_count] = bp;
(sp->buffer_total_count)++;
pvr2_buffer_set_idle(bp);
}
} else {
while (sp->buffer_total_count > cnt) {
struct pvr2_buffer *bp;
bp = sp->buffers[sp->buffer_total_count - 1];
sp->buffers[sp->buffer_total_count - 1] = NULL;
(sp->buffer_total_count)--;
pvr2_buffer_done(bp);
kfree(bp);
}
if (scnt < sp->buffer_slot_count) {
struct pvr2_buffer **nb = NULL;
if (scnt) {
nb = kmemdup(sp->buffers, scnt * sizeof(*nb),
GFP_KERNEL);
if (!nb) return -ENOMEM;
}
kfree(sp->buffers);
sp->buffers = nb;
sp->buffer_slot_count = scnt;
}
}
return 0;
}
static int pvr2_stream_achieve_buffer_count(struct pvr2_stream *sp)
{
struct pvr2_buffer *bp;
unsigned int cnt;
if (sp->buffer_total_count == sp->buffer_target_count) return 0;
pvr2_trace(PVR2_TRACE_BUF_POOL,
"/*---TRACE_FLOW---*/ poolCheck stream=%p cur=%d tgt=%d",
sp, sp->buffer_total_count, sp->buffer_target_count);
if (sp->buffer_total_count < sp->buffer_target_count) {
return pvr2_stream_buffer_count(sp, sp->buffer_target_count);
}
cnt = 0;
while ((sp->buffer_total_count - cnt) > sp->buffer_target_count) {
bp = sp->buffers[sp->buffer_total_count - (cnt + 1)];
if (bp->state != pvr2_buffer_state_idle) break;
cnt++;
}
if (cnt) {
pvr2_stream_buffer_count(sp, sp->buffer_total_count - cnt);
}
return 0;
}
static void pvr2_stream_internal_flush(struct pvr2_stream *sp)
{
struct list_head *lp;
struct pvr2_buffer *bp1;
while ((lp = sp->queued_list.next) != &sp->queued_list) {
bp1 = list_entry(lp, struct pvr2_buffer, list_overhead);
pvr2_buffer_wipe(bp1);
if (bp1->state != pvr2_buffer_state_queued) continue;
pvr2_buffer_set_idle(bp1);
}
if (sp->buffer_total_count != sp->buffer_target_count) {
pvr2_stream_achieve_buffer_count(sp);
}
}
static void pvr2_stream_init(struct pvr2_stream *sp)
{
spin_lock_init(&sp->list_lock);
mutex_init(&sp->mutex);
INIT_LIST_HEAD(&sp->queued_list);
INIT_LIST_HEAD(&sp->ready_list);
INIT_LIST_HEAD(&sp->idle_list);
}
static void pvr2_stream_done(struct pvr2_stream *sp)
{
mutex_lock(&sp->mutex); do {
pvr2_stream_internal_flush(sp);
pvr2_stream_buffer_count(sp, 0);
} while (0); mutex_unlock(&sp->mutex);
}
static void buffer_complete(struct urb *urb)
{
struct pvr2_buffer *bp = urb->context;
struct pvr2_stream *sp;
unsigned long irq_flags;
BUFFER_CHECK(bp);
sp = bp->stream;
bp->used_count = 0;
bp->status = 0;
pvr2_trace(PVR2_TRACE_BUF_FLOW,
"/*---TRACE_FLOW---*/ bufferComplete %p stat=%d cnt=%d",
bp, urb->status, urb->actual_length);
spin_lock_irqsave(&sp->list_lock, irq_flags);
if ((!(urb->status)) ||
(urb->status == -ENOENT) ||
(urb->status == -ECONNRESET) ||
(urb->status == -ESHUTDOWN)) {
(sp->buffers_processed)++;
sp->bytes_processed += urb->actual_length;
bp->used_count = urb->actual_length;
if (sp->fail_count) {
pvr2_trace(PVR2_TRACE_TOLERANCE,
"stream %p transfer ok - fail count reset",
sp);
sp->fail_count = 0;
}
} else if (sp->fail_count < sp->fail_tolerance) {
(sp->fail_count)++;
(sp->buffers_failed)++;
pvr2_trace(PVR2_TRACE_TOLERANCE,
"stream %p ignoring error %d - fail count increased to %u",
sp, urb->status, sp->fail_count);
} else {
(sp->buffers_failed)++;
bp->status = urb->status;
}
spin_unlock_irqrestore(&sp->list_lock, irq_flags);
pvr2_buffer_set_ready(bp);
if (sp->callback_func) {
sp->callback_func(sp->callback_data);
}
}
struct pvr2_stream *pvr2_stream_create(void)
{
struct pvr2_stream *sp;
sp = kzalloc(sizeof(*sp), GFP_KERNEL);
if (!sp) return sp;
pvr2_trace(PVR2_TRACE_INIT, "pvr2_stream_create: sp=%p", sp);
pvr2_stream_init(sp);
return sp;
}
void pvr2_stream_destroy(struct pvr2_stream *sp)
{
if (!sp) return;
pvr2_trace(PVR2_TRACE_INIT, "pvr2_stream_destroy: sp=%p", sp);
pvr2_stream_done(sp);
kfree(sp);
}
void pvr2_stream_setup(struct pvr2_stream *sp,
struct usb_device *dev,
int endpoint,
unsigned int tolerance)
{
mutex_lock(&sp->mutex); do {
pvr2_stream_internal_flush(sp);
sp->dev = dev;
sp->endpoint = endpoint;
sp->fail_tolerance = tolerance;
} while (0); mutex_unlock(&sp->mutex);
}
void pvr2_stream_set_callback(struct pvr2_stream *sp,
pvr2_stream_callback func,
void *data)
{
unsigned long irq_flags;
mutex_lock(&sp->mutex);
do {
spin_lock_irqsave(&sp->list_lock, irq_flags);
sp->callback_data = data;
sp->callback_func = func;
spin_unlock_irqrestore(&sp->list_lock, irq_flags);
} while (0);
mutex_unlock(&sp->mutex);
}
void pvr2_stream_get_stats(struct pvr2_stream *sp,
struct pvr2_stream_stats *stats,
int zero_counts)
{
unsigned long irq_flags;
spin_lock_irqsave(&sp->list_lock, irq_flags);
if (stats) {
stats->buffers_in_queue = sp->q_count;
stats->buffers_in_idle = sp->i_count;
stats->buffers_in_ready = sp->r_count;
stats->buffers_processed = sp->buffers_processed;
stats->buffers_failed = sp->buffers_failed;
stats->bytes_processed = sp->bytes_processed;
}
if (zero_counts) {
sp->buffers_processed = 0;
sp->buffers_failed = 0;
sp->bytes_processed = 0;
}
spin_unlock_irqrestore(&sp->list_lock, irq_flags);
}
int pvr2_stream_get_buffer_count(struct pvr2_stream *sp)
{
return sp->buffer_target_count;
}
int pvr2_stream_set_buffer_count(struct pvr2_stream *sp, unsigned int cnt)
{
int ret;
if (sp->buffer_target_count == cnt) return 0;
mutex_lock(&sp->mutex);
do {
sp->buffer_target_count = cnt;
ret = pvr2_stream_achieve_buffer_count(sp);
} while (0);
mutex_unlock(&sp->mutex);
return ret;
}
struct pvr2_buffer *pvr2_stream_get_idle_buffer(struct pvr2_stream *sp)
{
struct list_head *lp = sp->idle_list.next;
if (lp == &sp->idle_list) return NULL;
return list_entry(lp, struct pvr2_buffer, list_overhead);
}
struct pvr2_buffer *pvr2_stream_get_ready_buffer(struct pvr2_stream *sp)
{
struct list_head *lp = sp->ready_list.next;
if (lp == &sp->ready_list) return NULL;
return list_entry(lp, struct pvr2_buffer, list_overhead);
}
struct pvr2_buffer *pvr2_stream_get_buffer(struct pvr2_stream *sp, int id)
{
if (id < 0) return NULL;
if (id >= sp->buffer_total_count) return NULL;
return sp->buffers[id];
}
int pvr2_stream_get_ready_count(struct pvr2_stream *sp)
{
return sp->r_count;
}
void pvr2_stream_kill(struct pvr2_stream *sp)
{
struct pvr2_buffer *bp;
mutex_lock(&sp->mutex);
do {
pvr2_stream_internal_flush(sp);
while ((bp = pvr2_stream_get_ready_buffer(sp)) != NULL) {
pvr2_buffer_set_idle(bp);
}
if (sp->buffer_total_count != sp->buffer_target_count) {
pvr2_stream_achieve_buffer_count(sp);
}
} while (0);
mutex_unlock(&sp->mutex);
}
int pvr2_buffer_queue(struct pvr2_buffer *bp)
{
#undef SEED_BUFFER
#ifdef SEED_BUFFER
unsigned int idx;
unsigned int val;
#endif
int ret = 0;
struct pvr2_stream *sp;
if (!bp) return -EINVAL;
sp = bp->stream;
mutex_lock(&sp->mutex);
do {
pvr2_buffer_wipe(bp);
if (!sp->dev) {
ret = -EIO;
break;
}
pvr2_buffer_set_queued(bp);
#ifdef SEED_BUFFER
for (idx = 0; idx < (bp->max_count) / 4; idx++) {
val = bp->id << 24;
val |= idx;
((unsigned int *)(bp->ptr))[idx] = val;
}
#endif
bp->status = -EINPROGRESS;
usb_fill_bulk_urb(bp->purb,
sp->dev,
usb_rcvbulkpipe(sp->dev, sp->endpoint),
bp->ptr,
bp->max_count,
buffer_complete,
bp);
usb_submit_urb(bp->purb, GFP_KERNEL);
} while (0);
mutex_unlock(&sp->mutex);
return ret;
}
int pvr2_buffer_set_buffer(struct pvr2_buffer *bp, void *ptr, unsigned int cnt)
{
int ret = 0;
unsigned long irq_flags;
struct pvr2_stream *sp;
if (!bp) return -EINVAL;
sp = bp->stream;
mutex_lock(&sp->mutex);
do {
spin_lock_irqsave(&sp->list_lock, irq_flags);
if (bp->state != pvr2_buffer_state_idle) {
ret = -EPERM;
} else {
bp->ptr = ptr;
bp->stream->i_bcount -= bp->max_count;
bp->max_count = cnt;
bp->stream->i_bcount += bp->max_count;
pvr2_trace(PVR2_TRACE_BUF_FLOW,
"/*---TRACE_FLOW---*/ bufferPool %8s cap cap=%07d cnt=%02d",
pvr2_buffer_state_decode(
pvr2_buffer_state_idle),
bp->stream->i_bcount, bp->stream->i_count);
}
spin_unlock_irqrestore(&sp->list_lock, irq_flags);
} while (0);
mutex_unlock(&sp->mutex);
return ret;
}
unsigned int pvr2_buffer_get_count(struct pvr2_buffer *bp)
{
return bp->used_count;
}
int pvr2_buffer_get_status(struct pvr2_buffer *bp)
{
return bp->status;
}
int pvr2_buffer_get_id(struct pvr2_buffer *bp)
{
return bp->id;
}