#include <sys/scsi/scsi.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/thread.h>
#include <sys/var.h>
#include "sd_xbuf.h"
static int xbuf_iostart(ddi_xbuf_attr_t xap);
static void xbuf_dispatch(ddi_xbuf_attr_t xap);
static void xbuf_restart_callback(void *arg);
static int xbuf_brk_done(struct buf *bp);
#define XBUF_TQ_MINALLOC 64
#define XBUF_TQ_MAXALLOC 512
#define XBUF_DISPATCH_DELAY (drv_usectohz(50000))
static taskq_t *xbuf_tq = NULL;
static int xbuf_attr_tq_minalloc = XBUF_TQ_MINALLOC;
static int xbuf_attr_tq_maxalloc = XBUF_TQ_MAXALLOC;
static kmutex_t xbuf_mutex = { 0 };
static uint32_t xbuf_refcount = 0;
struct xbuf_brk {
kmutex_t mutex;
struct buf *bp0;
uint8_t nbufs;
uint8_t active;
size_t brksize;
int brkblk;
off_t off;
off_t noff;
daddr_t blkno;
};
_NOTE(DATA_READABLE_WITHOUT_LOCK(xbuf_brk::off))
#define b_clone_private b_forw
DDII ddi_xbuf_attr_t
ddi_xbuf_attr_create(size_t xsize,
void (*xa_strategy)(struct buf *bp, ddi_xbuf_t xp, void *attr_arg),
void *attr_arg, uint32_t active_limit, uint32_t reserve_limit,
major_t major, int flags)
{
ddi_xbuf_attr_t xap;
xap = kmem_zalloc(sizeof (struct __ddi_xbuf_attr), KM_SLEEP);
mutex_init(&xap->xa_mutex, NULL, MUTEX_DRIVER, NULL);
mutex_init(&xap->xa_reserve_mutex, NULL, MUTEX_DRIVER, NULL);
xap->xa_allocsize = max(xsize, sizeof (void *));
xap->xa_active_limit = active_limit;
xap->xa_active_lowater = xap->xa_active_limit / 2;
xap->xa_reserve_limit = reserve_limit;
xap->xa_strategy = xa_strategy;
xap->xa_attr_arg = attr_arg;
mutex_enter(&xbuf_mutex);
if (xbuf_refcount == 0) {
ASSERT(xbuf_tq == NULL);
xbuf_tq = taskq_create("xbuf_taskq", ncpus,
(v.v_maxsyspri - 2), xbuf_attr_tq_minalloc,
xbuf_attr_tq_maxalloc, TASKQ_PREPOPULATE);
}
xbuf_refcount++;
mutex_exit(&xbuf_mutex);
xap->xa_tq = xbuf_tq;
return (xap);
}
DDII void
ddi_xbuf_attr_destroy(ddi_xbuf_attr_t xap)
{
ddi_xbuf_t xp;
mutex_destroy(&xap->xa_mutex);
mutex_destroy(&xap->xa_reserve_mutex);
while (xap->xa_reserve_count != 0) {
xp = xap->xa_reserve_headp;
xap->xa_reserve_headp = *((void **)xp);
xap->xa_reserve_count--;
kmem_free(xp, xap->xa_allocsize);
}
ASSERT(xap->xa_reserve_headp == NULL);
mutex_enter(&xbuf_mutex);
ASSERT((xbuf_refcount != 0) && (xbuf_tq != NULL));
xbuf_refcount--;
if (xbuf_refcount == 0) {
taskq_destroy(xbuf_tq);
xbuf_tq = NULL;
}
mutex_exit(&xbuf_mutex);
kmem_free(xap, sizeof (struct __ddi_xbuf_attr));
}
DDII void
ddi_xbuf_attr_register_devinfo(ddi_xbuf_attr_t xbuf_attr, dev_info_t *dip)
{
}
DDII void
ddi_xbuf_attr_unregister_devinfo(ddi_xbuf_attr_t xbuf_attr, dev_info_t *dip)
{
}
DDII int
ddi_xbuf_attr_setup_brk(ddi_xbuf_attr_t xap, size_t size)
{
if (size < DEV_BSIZE)
return (0);
mutex_enter(&xap->xa_mutex);
xap->xa_brksize = size & ~(DEV_BSIZE - 1);
mutex_exit(&xap->xa_mutex);
return (1);
}
DDII int
ddi_xbuf_qstrategy(struct buf *bp, ddi_xbuf_attr_t xap)
{
ASSERT(xap != NULL);
ASSERT(!mutex_owned(&xap->xa_mutex));
ASSERT(!mutex_owned(&xap->xa_reserve_mutex));
mutex_enter(&xap->xa_mutex);
ASSERT((bp->b_bcount & (DEV_BSIZE - 1)) == 0);
if (xap->xa_brksize && bp->b_bcount > xap->xa_brksize) {
struct xbuf_brk *brkp;
brkp = kmem_zalloc(sizeof (struct xbuf_brk), KM_SLEEP);
_NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(*brkp))
mutex_init(&brkp->mutex, NULL, MUTEX_DRIVER, NULL);
brkp->bp0 = bp;
brkp->brksize = xap->xa_brksize;
brkp->brkblk = btodt(xap->xa_brksize);
brkp->noff = xap->xa_brksize;
brkp->blkno = bp->b_blkno;
_NOTE(NOW_VISIBLE_TO_OTHER_THREADS(*brkp))
bp->b_private = brkp;
} else {
bp->b_private = NULL;
}
if (xap->xa_headp == NULL) {
xap->xa_headp = xap->xa_tailp = bp;
} else {
xap->xa_tailp->av_forw = bp;
xap->xa_tailp = bp;
}
bp->av_forw = NULL;
xap->xa_pending++;
mutex_exit(&xap->xa_mutex);
return (xbuf_iostart(xap));
}
DDII int
ddi_xbuf_done(struct buf *bp, ddi_xbuf_attr_t xap)
{
ddi_xbuf_t xp;
int done;
ASSERT(bp != NULL);
ASSERT(xap != NULL);
ASSERT(!mutex_owned(&xap->xa_mutex));
ASSERT(!mutex_owned(&xap->xa_reserve_mutex));
xp = ddi_xbuf_get(bp, xap);
mutex_enter(&xap->xa_mutex);
#ifdef SDDEBUG
if (xap->xa_active_limit != 0) {
ASSERT(xap->xa_active_count > 0);
}
#endif
xap->xa_active_count--;
if (xap->xa_reserve_limit != 0) {
mutex_enter(&xap->xa_reserve_mutex);
if (xap->xa_reserve_count < xap->xa_reserve_limit) {
*((void **)xp) = xap->xa_reserve_headp;
xap->xa_reserve_headp = xp;
xap->xa_reserve_count++;
mutex_exit(&xap->xa_reserve_mutex);
goto done;
}
mutex_exit(&xap->xa_reserve_mutex);
}
kmem_free(xp, xap->xa_allocsize);
done:
if (bp->b_iodone == xbuf_brk_done) {
struct xbuf_brk *brkp = (struct xbuf_brk *)bp->b_clone_private;
brkp->active--;
if (brkp->active || xap->xa_headp == brkp->bp0) {
done = 0;
} else {
brkp->off = -1;
done = 1;
}
} else {
done = 1;
}
if ((xap->xa_active_limit == 0) ||
(xap->xa_active_count <= xap->xa_active_lowater)) {
xbuf_dispatch(xap);
}
mutex_exit(&xap->xa_mutex);
return (done);
}
static int
xbuf_brk_done(struct buf *bp)
{
struct xbuf_brk *brkp = (struct xbuf_brk *)bp->b_clone_private;
struct buf *bp0 = brkp->bp0;
int done;
mutex_enter(&brkp->mutex);
if (bp->b_flags & B_ERROR && !(bp0->b_flags & B_ERROR)) {
bp0->b_flags |= B_ERROR;
bp0->b_error = bp->b_error;
}
if (bp->b_resid)
bp0->b_resid = bp0->b_bcount;
freerbuf(bp);
brkp->nbufs--;
done = (brkp->off == -1 && brkp->nbufs == 0);
mutex_exit(&brkp->mutex);
if (done) {
mutex_destroy(&brkp->mutex);
kmem_free(brkp, sizeof (struct xbuf_brk));
biodone(bp0);
}
return (0);
}
DDII void
ddi_xbuf_dispatch(ddi_xbuf_attr_t xap)
{
mutex_enter(&xap->xa_mutex);
if ((xap->xa_active_limit == 0) ||
(xap->xa_active_count <= xap->xa_active_lowater)) {
xbuf_dispatch(xap);
}
mutex_exit(&xap->xa_mutex);
}
DDII ddi_xbuf_t
ddi_xbuf_get(struct buf *bp, ddi_xbuf_attr_t xap)
{
return (bp->b_private);
}
static int
xbuf_iostart(ddi_xbuf_attr_t xap)
{
struct buf *bp;
ddi_xbuf_t xp;
ASSERT(xap != NULL);
ASSERT(!mutex_owned(&xap->xa_mutex));
ASSERT(!mutex_owned(&xap->xa_reserve_mutex));
for (;;) {
mutex_enter(&xap->xa_mutex);
if ((bp = xap->xa_headp) == NULL) {
break;
}
if ((xap->xa_active_limit != 0) &&
(xap->xa_active_count >= xap->xa_active_limit)) {
break;
}
if (xap->xa_reserve_limit != 0) {
mutex_enter(&xap->xa_reserve_mutex);
if (xap->xa_reserve_count != 0) {
ASSERT(xap->xa_reserve_headp != NULL);
xp = xap->xa_reserve_headp;
xap->xa_reserve_headp = *((void **)xp);
ASSERT(xap->xa_reserve_count > 0);
xap->xa_reserve_count--;
} else {
while (xap->xa_reserve_count <
xap->xa_reserve_limit) {
xp = kmem_alloc(xap->xa_allocsize,
KM_NOSLEEP);
if (xp == NULL) {
break;
}
*((void **)xp) = xap->xa_reserve_headp;
xap->xa_reserve_headp = xp;
xap->xa_reserve_count++;
}
xp = kmem_alloc(xap->xa_allocsize, KM_NOSLEEP);
}
mutex_exit(&xap->xa_reserve_mutex);
} else {
xp = kmem_alloc(xap->xa_allocsize, KM_NOSLEEP);
}
if (xp == NULL) {
break;
}
xap->xa_active_count++;
if (bp->b_private) {
struct xbuf_brk *brkp = bp->b_private;
struct buf *bp0 = bp;
brkp->active++;
mutex_enter(&brkp->mutex);
brkp->nbufs++;
mutex_exit(&brkp->mutex);
if (brkp->noff < bp0->b_bcount) {
bp = bioclone(bp0, brkp->off, brkp->brksize,
bp0->b_edev, brkp->blkno, xbuf_brk_done,
NULL, KM_SLEEP);
brkp->off = brkp->noff;
brkp->noff += brkp->brksize;
brkp->blkno += brkp->brkblk;
} else {
bp = bioclone(bp0, brkp->off,
bp0->b_bcount - brkp->off, bp0->b_edev,
brkp->blkno, xbuf_brk_done, NULL, KM_SLEEP);
xap->xa_headp = bp0->av_forw;
bp0->av_forw = NULL;
}
bp->b_clone_private = (struct buf *)brkp;
} else {
xap->xa_headp = bp->av_forw;
bp->av_forw = NULL;
}
bp->b_private = xp;
mutex_exit(&xap->xa_mutex);
(*(xap->xa_strategy))(bp, xp, xap->xa_attr_arg);
}
ASSERT(xap->xa_pending > 0);
xap->xa_pending--;
mutex_exit(&xap->xa_mutex);
return (0);
}
static void
xbuf_taskq_cb(void *arg)
{
(void) xbuf_iostart(arg);
}
static void
xbuf_dispatch(ddi_xbuf_attr_t xap)
{
ASSERT(xap != NULL);
ASSERT(xap->xa_tq != NULL);
ASSERT(mutex_owned(&xap->xa_mutex));
if ((xap->xa_headp != NULL) && (xap->xa_timeid == NULL) &&
(xap->xa_pending == 0)) {
if (taskq_dispatch(xap->xa_tq,
xbuf_taskq_cb, xap, KM_NOSLEEP) == TASKQID_INVALID) {
xap->xa_timeid = timeout(xbuf_restart_callback, xap,
XBUF_DISPATCH_DELAY);
} else {
xap->xa_pending++;
}
}
}
static void
xbuf_restart_callback(void *arg)
{
ddi_xbuf_attr_t xap = arg;
ASSERT(xap != NULL);
ASSERT(xap->xa_tq != NULL);
ASSERT(!mutex_owned(&xap->xa_mutex));
mutex_enter(&xap->xa_mutex);
xap->xa_timeid = NULL;
xbuf_dispatch(xap);
mutex_exit(&xap->xa_mutex);
}
DDII void
ddi_xbuf_flushq(ddi_xbuf_attr_t xap, int (*funcp)(struct buf *))
{
struct buf *bp;
struct buf *next_bp;
struct buf *prev_bp = NULL;
ASSERT(xap != NULL);
ASSERT(xap->xa_tq != NULL);
ASSERT(!mutex_owned(&xap->xa_mutex));
mutex_enter(&xap->xa_mutex);
for (bp = xap->xa_headp; bp != NULL; bp = next_bp) {
next_bp = bp->av_forw;
if ((funcp != NULL) && (!(*funcp)(bp))) {
prev_bp = bp;
continue;
}
if (bp == xap->xa_headp) {
xap->xa_headp = next_bp;
if (xap->xa_headp == NULL) {
xap->xa_tailp = NULL;
}
} else {
ASSERT(xap->xa_headp != NULL);
ASSERT(prev_bp != NULL);
if (bp == xap->xa_tailp) {
ASSERT(next_bp == NULL);
xap->xa_tailp = prev_bp;
}
prev_bp->av_forw = next_bp;
}
bp->av_forw = NULL;
if (xap->xa_flush_headp == NULL) {
ASSERT(xap->xa_flush_tailp == NULL);
xap->xa_flush_headp = xap->xa_flush_tailp = bp;
} else {
ASSERT(xap->xa_flush_tailp != NULL);
xap->xa_flush_tailp->av_forw = bp;
xap->xa_flush_tailp = bp;
}
}
while ((bp = xap->xa_flush_headp) != NULL) {
xap->xa_flush_headp = bp->av_forw;
if (xap->xa_flush_headp == NULL) {
xap->xa_flush_tailp = NULL;
}
mutex_exit(&xap->xa_mutex);
bioerror(bp, EIO);
bp->b_resid = bp->b_bcount;
biodone(bp);
mutex_enter(&xap->xa_mutex);
}
mutex_exit(&xap->xa_mutex);
}