#include "opt_debug_cluster.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/buf.h>
#include <sys/vnode.h>
#include <sys/malloc.h>
#include <sys/mount.h>
#include <sys/resourcevar.h>
#include <sys/vmmeter.h>
#include <vm/vm.h>
#include <vm/vm_object.h>
#include <vm/vm_page.h>
#include <sys/sysctl.h>
#include <sys/buf2.h>
#include <vm/vm_page2.h>
#include <machine/limits.h>
struct cluster_cache {
struct vnode *vp;
u_int locked;
off_t v_lastw;
off_t v_cstart;
off_t v_lasta;
u_int v_clen;
u_int iterator;
} __cachealign;
typedef struct cluster_cache cluster_cache_t;
#define CLUSTER_CACHE_SIZE 512
#define CLUSTER_CACHE_MASK (CLUSTER_CACHE_SIZE - 1)
#define CLUSTER_ZONE ((off_t)(1024 * 1024))
cluster_cache_t cluster_array[CLUSTER_CACHE_SIZE];
#if defined(CLUSTERDEBUG)
static int rcluster= 0;
SYSCTL_INT(_debug, OID_AUTO, rcluster, CTLFLAG_RW, &rcluster, 0, "");
#endif
static MALLOC_DEFINE(M_SEGMENT, "cluster_save", "cluster_save buffer");
static struct cluster_save *
cluster_collectbufs (cluster_cache_t *cc, struct vnode *vp,
struct buf *last_bp, int blksize);
static struct buf *
cluster_rbuild (struct vnode *vp, off_t filesize, off_t loffset,
off_t doffset, int blksize, int run,
struct buf *fbp, int *srp);
static void cluster_callback (struct bio *);
static void cluster_setram (struct buf *);
static void cluster_clrram (struct buf *);
static int cluster_wbuild(struct vnode *vp, struct buf **bpp, int blksize,
off_t start_loffset, int bytes);
static int write_behind = 1;
SYSCTL_INT(_vfs, OID_AUTO, write_behind, CTLFLAG_RW, &write_behind, 0,
"Cluster write-behind setting");
static quad_t write_behind_minfilesize = 10 * 1024 * 1024;
SYSCTL_QUAD(_vfs, OID_AUTO, write_behind_minfilesize, CTLFLAG_RW,
&write_behind_minfilesize, 0, "Cluster write-behind setting");
static int max_readahead = 2 * 1024 * 1024;
SYSCTL_INT(_vfs, OID_AUTO, max_readahead, CTLFLAG_RW, &max_readahead, 0,
"Limit in bytes for desired cluster read-ahead");
extern vm_page_t bogus_page;
static __inline
int
calc_rbuild_reqsize(int maxra, int maxrbuild)
{
int nblks;
if ((nblks = maxra / 4) > maxrbuild)
nblks = maxrbuild;
if (nblks < 1)
nblks = maxra;
return nblks;
}
static
cluster_cache_t *
cluster_getcache(cluster_cache_t *dummy, struct vnode *vp, off_t loffset)
{
cluster_cache_t *cc;
size_t hv;
int i;
int xact;
hv = (size_t)(intptr_t)vp ^ (size_t)(intptr_t)vp / sizeof(*vp);
hv &= CLUSTER_CACHE_MASK & ~3;
cc = &cluster_array[hv];
xact = -1;
for (i = 0; i < 4; ++i) {
if (cc[i].vp != vp)
continue;
if (rounddown2(cc[i].v_cstart ^ loffset, CLUSTER_ZONE) == 0) {
xact = i;
break;
}
}
if (xact >= 0 && atomic_swap_int(&cc[xact].locked, 1) == 0) {
if (cc[xact].vp == vp &&
rounddown2(cc[i].v_cstart ^ loffset, CLUSTER_ZONE) == 0) {
return(&cc[xact]);
}
atomic_swap_int(&cc[xact].locked, 0);
}
i = cc->iterator++ & 3;
cc += i;
if (atomic_swap_int(&cc->locked, 1) != 0) {
cc = dummy;
cc->locked = 1;
cc->vp = NULL;
}
if (cc->vp != vp) {
cc->vp = vp;
cc->v_lasta = 0;
cc->v_clen = 0;
cc->v_cstart = 0;
cc->v_lastw = 0;
}
return(cc);
}
static
void
cluster_putcache(cluster_cache_t *cc)
{
atomic_swap_int(&cc->locked, 0);
}
int
cluster_readx(struct vnode *vp, off_t filesize, off_t loffset, int blksize,
int bflags, size_t minreq, size_t maxreq,
struct buf **bpp)
{
struct buf *bp, *rbp, *reqbp;
off_t origoffset;
off_t doffset;
int error;
int i;
int maxra;
int maxrbuild;
int sr;
int blkflags = (bflags & B_KVABIO) ? GETBLK_KVABIO : 0;
sr = 0;
if (maxreq < minreq)
maxreq = minreq;
if (maxreq > max_readahead) {
maxreq = max_readahead;
if (maxreq > 16 * 1024 * 1024)
maxreq = 16 * 1024 * 1024;
}
if (maxreq < blksize)
maxreq = blksize;
if (loffset + maxreq > filesize) {
if (loffset > filesize)
maxreq = 0;
else
maxreq = filesize - loffset;
}
maxra = (int)(maxreq / blksize);
if (*bpp)
reqbp = bp = *bpp;
else
*bpp = reqbp = bp = getblk(vp, loffset, blksize, blkflags, 0);
origoffset = loffset;
maxrbuild = vmaxiosize(vp) / blksize;
if (bp->b_flags & B_CACHE) {
if (maxra <= 1)
return 0;
if ((bp->b_flags & B_RAM) == 0)
return 0;
bp->b_flags &= ~B_RAM;
i = maxra / 2;
rbp = findblk(vp, loffset + i * blksize, FINDBLK_TEST);
if (rbp == NULL || (rbp->b_flags & B_CACHE) == 0) {
while (i) {
--i;
rbp = findblk(vp, loffset + i * blksize,
FINDBLK_TEST);
if (rbp) {
++i;
break;
}
}
} else {
while (i < maxra) {
rbp = findblk(vp, loffset + i * blksize,
FINDBLK_TEST);
if (rbp == NULL)
break;
++i;
}
}
if (i >= maxra)
return 0;
loffset += i * blksize;
reqbp = bp = NULL;
if (loffset >= filesize)
return 0;
if (loffset + maxra * blksize > filesize) {
maxreq = filesize - loffset;
maxra = (int)(maxreq / blksize);
}
sr = 1;
} else {
__debugvar off_t firstread = bp->b_loffset;
int nblks;
bp->b_cmd = BUF_CMD_READ;
bp->b_bio1.bio_done = biodone_sync;
bp->b_bio1.bio_flags |= BIO_SYNC;
KASSERT(firstread != NOOFFSET,
("cluster_read: no buffer offset"));
nblks = calc_rbuild_reqsize(maxra, maxrbuild);
sr = (maxra + 1) / 2;
if (nblks > 1) {
int burstbytes;
error = VOP_BMAP(vp, loffset, &doffset,
&burstbytes, NULL, BUF_CMD_READ);
if (error)
goto single_block_read;
if (nblks > burstbytes / blksize)
nblks = burstbytes / blksize;
if (doffset == NOOFFSET)
goto single_block_read;
if (nblks <= 1)
goto single_block_read;
bp = cluster_rbuild(vp, filesize, loffset,
doffset, blksize, nblks, bp, &sr);
loffset += bp->b_bufsize;
maxra -= bp->b_bufsize / blksize;
} else {
single_block_read:
loffset += blksize;
--maxra;
}
}
if (bp) {
#if defined(CLUSTERDEBUG)
if (rcluster)
kprintf("S(%012jx,%d,%d)\n",
(intmax_t)bp->b_loffset, bp->b_bcount, maxra);
#endif
if ((bp->b_flags & B_CLUSTER) == 0)
vfs_busy_pages(vp, bp);
bp->b_flags &= ~(B_ERROR | B_INVAL | B_NOTMETA);
bp->b_flags |= bflags;
vn_strategy(vp, &bp->b_bio1);
bp = NULL;
}
#if defined(CLUSTERDEBUG)
if (rcluster)
kprintf("cluster_rd %016jx/%d maxra=%d sr=%d\n",
loffset, blksize, maxra, sr);
#endif
while (maxra > 0) {
int burstbytes;
int nblks;
rbp = getblk(vp, loffset, blksize,
GETBLK_SZMATCH | GETBLK_NOWAIT | GETBLK_KVABIO,
0);
#if defined(CLUSTERDEBUG)
if (rcluster) {
kprintf("read-ahead %016jx rbp=%p ",
loffset, rbp);
}
#endif
if (rbp == NULL)
goto no_read_ahead;
if ((rbp->b_flags & B_CACHE)) {
bqrelse(rbp);
goto no_read_ahead;
}
error = VOP_BMAP(vp, loffset, &doffset,
&burstbytes, NULL, BUF_CMD_READ);
if (error || doffset == NOOFFSET) {
nblks = 1;
doffset = NOOFFSET;
} else {
nblks = calc_rbuild_reqsize(maxra, maxrbuild);
if (nblks > burstbytes / blksize)
nblks = burstbytes / blksize;
}
rbp->b_cmd = BUF_CMD_READ;
if (nblks > 1) {
rbp = cluster_rbuild(vp, filesize, loffset,
doffset, blksize,
nblks, rbp, &sr);
} else {
rbp->b_bio2.bio_offset = doffset;
if (--sr == 0)
cluster_setram(rbp);
}
rbp->b_flags &= ~(B_ERROR | B_INVAL | B_NOTMETA);
rbp->b_flags |= bflags;
if ((rbp->b_flags & B_CLUSTER) == 0)
vfs_busy_pages(vp, rbp);
BUF_KERNPROC(rbp);
loffset += rbp->b_bufsize;
maxra -= rbp->b_bufsize / blksize;
vn_strategy(vp, &rbp->b_bio1);
}
no_read_ahead:
if (reqbp) {
KKASSERT(reqbp->b_bio1.bio_flags & BIO_SYNC);
error = biowait(&reqbp->b_bio1, "clurd");
} else {
error = 0;
}
return (error);
}
void
cluster_readcb(struct vnode *vp, off_t filesize, off_t loffset, int blksize,
int bflags, size_t minreq, size_t maxreq,
void (*func)(struct bio *), void *arg)
{
struct buf *bp, *rbp, *reqbp;
off_t origoffset;
off_t doffset;
int i;
int maxra;
int maxrbuild;
int sr;
int blkflags = (bflags & B_KVABIO) ? GETBLK_KVABIO : 0;
sr = 0;
if (maxreq < minreq)
maxreq = minreq;
if (maxreq > max_readahead) {
maxreq = max_readahead;
if (maxreq > 16 * 1024 * 1024)
maxreq = 16 * 1024 * 1024;
}
if (maxreq < blksize)
maxreq = blksize;
if (loffset + maxreq > filesize) {
if (loffset > filesize)
maxreq = 0;
else
maxreq = filesize - loffset;
}
maxra = (int)(maxreq / blksize);
reqbp = bp = getblk(vp, loffset, blksize, blkflags, 0);
origoffset = loffset;
maxrbuild = vmaxiosize(vp) / blksize;
if (bp->b_flags & B_CACHE) {
bp->b_bio1.bio_caller_info1.ptr = arg;
bp->b_bio1.bio_flags |= BIO_DONE;
if (maxra <= 1)
goto no_read_ahead;
if ((bp->b_flags & B_RAM) == 0)
goto no_read_ahead;
bp->b_flags &= ~B_RAM;
if (findblk(vp, loffset + (maxra - 2) * blksize, FINDBLK_TEST))
i = maxra - 1;
else
i = 1;
while (i < maxra) {
if (findblk(vp, loffset + i * blksize,
FINDBLK_TEST) == NULL) {
break;
}
++i;
}
if (i >= maxra)
goto no_read_ahead;
loffset += i * blksize;
bp = NULL;
if (loffset >= filesize)
goto no_read_ahead;
if (loffset + maxra * blksize > filesize) {
maxreq = filesize - loffset;
maxra = (int)(maxreq / blksize);
}
sr = 1;
} else {
__debugvar off_t firstread = bp->b_loffset;
int nblks;
int error;
bp->b_flags &= ~(B_ERROR | B_EINTR | B_INVAL | B_NOTMETA);
bp->b_flags |= bflags;
bp->b_cmd = BUF_CMD_READ;
bp->b_bio1.bio_done = func;
bp->b_bio1.bio_caller_info1.ptr = arg;
BUF_KERNPROC(bp);
reqbp = NULL;
KASSERT(firstread != NOOFFSET,
("cluster_read: no buffer offset"));
nblks = calc_rbuild_reqsize(maxra, maxrbuild);
sr = (maxra + 1) / 2;
if (nblks > 1) {
int burstbytes;
error = VOP_BMAP(vp, loffset, &doffset,
&burstbytes, NULL, BUF_CMD_READ);
if (error)
goto single_block_read;
if (nblks > burstbytes / blksize)
nblks = burstbytes / blksize;
if (doffset == NOOFFSET)
goto single_block_read;
if (nblks <= 1)
goto single_block_read;
bp = cluster_rbuild(vp, filesize, loffset,
doffset, blksize, nblks, bp, &sr);
loffset += bp->b_bufsize;
maxra -= bp->b_bufsize / blksize;
} else {
single_block_read:
loffset += blksize;
--maxra;
}
}
if (bp) {
#if defined(CLUSTERDEBUG)
if (rcluster)
kprintf("S(%012jx,%d,%d)\n",
(intmax_t)bp->b_loffset, bp->b_bcount, maxra);
#endif
if ((bp->b_flags & B_CLUSTER) == 0)
vfs_busy_pages(vp, bp);
bp->b_flags &= ~(B_ERROR | B_INVAL | B_NOTMETA);
bp->b_flags |= bflags;
vn_strategy(vp, &bp->b_bio1);
bp = NULL;
}
#if defined(CLUSTERDEBUG)
if (rcluster)
kprintf("cluster_rd %016jx/%d maxra=%d sr=%d\n",
loffset, blksize, maxra, sr);
#endif
while (maxra > 0) {
int burstbytes;
int error;
int nblks;
rbp = getblk(vp, loffset, blksize,
GETBLK_SZMATCH | GETBLK_NOWAIT | GETBLK_KVABIO,
0);
if (rbp == NULL)
goto no_read_ahead;
if ((rbp->b_flags & B_CACHE)) {
bqrelse(rbp);
goto no_read_ahead;
}
error = VOP_BMAP(vp, loffset, &doffset,
&burstbytes, NULL, BUF_CMD_READ);
if (error || doffset == NOOFFSET) {
nblks = 1;
doffset = NOOFFSET;
} else {
nblks = calc_rbuild_reqsize(maxra, maxrbuild);
if (nblks > burstbytes / blksize)
nblks = burstbytes / blksize;
}
rbp->b_cmd = BUF_CMD_READ;
if (nblks > 1) {
rbp = cluster_rbuild(vp, filesize, loffset,
doffset, blksize,
nblks, rbp, &sr);
} else {
rbp->b_bio2.bio_offset = doffset;
if (--sr == 0)
cluster_setram(rbp);
}
rbp->b_flags &= ~(B_ERROR | B_INVAL | B_NOTMETA);
rbp->b_flags |= bflags;
if ((rbp->b_flags & B_CLUSTER) == 0)
vfs_busy_pages(vp, rbp);
BUF_KERNPROC(rbp);
loffset += rbp->b_bufsize;
maxra -= rbp->b_bufsize / blksize;
vn_strategy(vp, &rbp->b_bio1);
}
no_read_ahead:
if (reqbp)
func(&reqbp->b_bio1);
}
static struct buf *
cluster_rbuild(struct vnode *vp, off_t filesize, off_t loffset, off_t doffset,
int blksize, int run, struct buf *fbp, int *srp)
{
struct buf *bp, *tbp;
off_t boffset;
int i, j;
int maxiosize = vmaxiosize(vp);
while (loffset + run * blksize > filesize) {
--run;
}
tbp = fbp;
tbp->b_bio2.bio_offset = doffset;
if (((tbp->b_flags & B_VMIO) == 0) || (run <= 1)) {
if (--*srp == 0)
cluster_setram(tbp);
else
cluster_clrram(tbp);
return tbp;
}
if (vp->v_type == VCHR || vp->v_type == VBLK)
bp = trypbuf_kva(&vp->v_pbuf_count);
else
bp = trypbuf_kva(&vp->v_mount->mnt_pbuf_count);
if (bp == NULL)
return tbp;
bp->b_vp = vp;
bp->b_data = (char *)((vm_offset_t)bp->b_data |
((vm_offset_t)tbp->b_data & PAGE_MASK));
bp->b_flags |= B_CLUSTER | B_VMIO | B_KVABIO;
bp->b_cmd = BUF_CMD_READ;
bp->b_bio1.bio_done = cluster_callback;
bp->b_bio1.bio_caller_info1.cluster_head = NULL;
bp->b_bio1.bio_caller_info2.cluster_tail = NULL;
bp->b_loffset = loffset;
bp->b_bio2.bio_offset = doffset;
KASSERT(bp->b_loffset != NOOFFSET,
("cluster_rbuild: no buffer offset"));
bp->b_bcount = 0;
bp->b_bufsize = 0;
bp->b_xio.xio_npages = 0;
for (boffset = doffset, i = 0; i < run; ++i, boffset += blksize) {
if (i) {
if ((bp->b_xio.xio_npages * PAGE_SIZE) +
round_page(blksize) > maxiosize) {
break;
}
tbp = getblk(vp, loffset + i * blksize, blksize,
GETBLK_SZMATCH |
GETBLK_NOWAIT |
GETBLK_KVABIO,
0);
if (tbp == NULL)
break;
for (j = 0; j < tbp->b_xio.xio_npages; j++) {
if (tbp->b_xio.xio_pages[j]->valid)
break;
}
if (j != tbp->b_xio.xio_npages) {
bqrelse(tbp);
break;
}
if ((tbp->b_flags & (B_CACHE|B_LOCKED)) ||
(tbp->b_flags & B_VMIO) == 0 ||
(LIST_FIRST(&tbp->b_dep) != NULL &&
buf_checkread(tbp))
) {
bqrelse(tbp);
break;
}
for (j = 0;j < tbp->b_xio.xio_npages; j++) {
if (tbp->b_xio.xio_pages[j]->valid)
break;
}
if (j != tbp->b_xio.xio_npages) {
bqrelse(tbp);
break;
}
if (tbp->b_bio2.bio_offset == NOOFFSET) {
tbp->b_bio2.bio_offset = boffset;
} else if (tbp->b_bio2.bio_offset != boffset) {
brelse(tbp);
break;
}
}
if (--*srp == 0)
cluster_setram(tbp);
else
cluster_clrram(tbp);
tbp->b_cmd = BUF_CMD_READ;
BUF_KERNPROC(tbp);
cluster_append(&bp->b_bio1, tbp);
for (j = 0; j < tbp->b_xio.xio_npages; ++j) {
vm_page_t m;
m = tbp->b_xio.xio_pages[j];
vm_page_busy_wait(m, FALSE, "clurpg");
vm_page_io_start(m);
vm_page_wakeup(m);
vm_object_pip_add(m->object, 1);
if ((bp->b_xio.xio_npages == 0) ||
(bp->b_xio.xio_pages[bp->b_xio.xio_npages-1] != m)) {
bp->b_xio.xio_pages[bp->b_xio.xio_npages] = m;
bp->b_xio.xio_npages++;
}
if ((m->valid & VM_PAGE_BITS_ALL) == VM_PAGE_BITS_ALL) {
tbp->b_xio.xio_pages[j] = bogus_page;
tbp->b_flags |= B_HASBOGUS;
}
}
if (tbp->b_bcount != blksize)
kprintf("warning: tbp->b_bcount wrong %d vs %d\n", tbp->b_bcount, blksize);
if (tbp->b_bufsize != blksize)
kprintf("warning: tbp->b_bufsize wrong %d vs %d\n", tbp->b_bufsize, blksize);
bp->b_bcount += blksize;
bp->b_bufsize += blksize;
}
for (j = 0; j < bp->b_xio.xio_npages; j++) {
if ((bp->b_xio.xio_pages[j]->valid & VM_PAGE_BITS_ALL) ==
VM_PAGE_BITS_ALL) {
bp->b_xio.xio_pages[j] = bogus_page;
bp->b_flags |= B_HASBOGUS;
}
}
if (bp->b_bufsize > bp->b_kvasize) {
panic("cluster_rbuild: b_bufsize(%d) > b_kvasize(%d)",
bp->b_bufsize, bp->b_kvasize);
}
pmap_qenter_noinval(trunc_page((vm_offset_t)bp->b_data),
(vm_page_t *)bp->b_xio.xio_pages,
bp->b_xio.xio_npages);
BUF_KERNPROC(bp);
return (bp);
}
static void
cluster_callback(struct bio *bio)
{
struct buf *bp = bio->bio_buf;
struct buf *tbp;
struct buf *next;
struct vnode *vp;
int error = 0;
int bpflags;
if (bp->b_flags & B_ERROR) {
error = bp->b_error;
} else if (bp->b_bcount != bp->b_bufsize) {
panic("cluster_callback: unexpected EOF on cluster %p!", bio);
}
pmap_qremove_noinval(trunc_page((vm_offset_t) bp->b_data),
bp->b_xio.xio_npages);
tbp = bio->bio_caller_info1.cluster_head;
bio->bio_caller_info1.cluster_head = NULL;
bpflags = bp->b_flags;
vp = bp->b_vp;
bp->b_vp = NULL;
if (vp->v_type == VCHR || vp->v_type == VBLK)
relpbuf(bp, &vp->v_pbuf_count);
else
relpbuf(bp, &vp->v_mount->mnt_pbuf_count);
bp = NULL;
while (tbp) {
next = tbp->b_cluster_next;
if (error) {
tbp->b_flags |= B_ERROR | B_IOISSUED;
tbp->b_error = error;
} else {
tbp->b_dirtyoff = tbp->b_dirtyend = 0;
tbp->b_flags &= ~(B_ERROR | B_INVAL);
if (tbp->b_cmd == BUF_CMD_READ) {
tbp->b_flags = (tbp->b_flags & ~B_NOTMETA) |
(bpflags & B_NOTMETA);
}
tbp->b_flags |= B_IOISSUED;
if (tbp->b_flags & B_DIRECT)
tbp->b_flags |= B_RELBUF;
if (tbp->b_cmd == BUF_CMD_WRITE)
bundirty(tbp);
}
biodone(&tbp->b_bio1);
tbp = next;
}
}
static __inline int
cluster_wbuild_wb(struct vnode *vp, int blksize, off_t start_loffset, int len)
{
int r = 0;
switch(write_behind) {
case 2:
if (start_loffset < len)
break;
start_loffset -= len;
case 1:
if (vp->v_filesize >= write_behind_minfilesize) {
r = cluster_wbuild(vp, NULL, blksize,
start_loffset, len);
}
default:
break;
}
return(r);
}
void
cluster_write(struct buf *bp, off_t filesize, int blksize, int seqcount)
{
struct vnode *vp;
off_t loffset;
int maxclen, cursize;
int async;
cluster_cache_t dummy;
cluster_cache_t *cc;
vp = bp->b_vp;
if (vp->v_type == VREG)
async = vp->v_mount->mnt_flag & MNT_ASYNC;
else
async = 0;
loffset = bp->b_loffset;
KASSERT(bp->b_loffset != NOOFFSET,
("cluster_write: no buffer offset"));
cc = cluster_getcache(&dummy, vp, loffset);
if (loffset == 0)
cc->v_lasta = cc->v_clen = cc->v_cstart = cc->v_lastw = 0;
if (cc->v_clen == 0 || loffset != cc->v_lastw ||
(bp->b_bio2.bio_offset != NOOFFSET &&
(bp->b_bio2.bio_offset != cc->v_lasta))) {
maxclen = vmaxiosize(vp);
if (cc->v_clen != 0) {
cursize = cc->v_lastw - cc->v_cstart;
if (bp->b_loffset + blksize < filesize ||
loffset != cc->v_lastw ||
cc->v_clen <= cursize) {
if (!async && seqcount > 0) {
cluster_wbuild_wb(vp, blksize,
cc->v_cstart, cursize);
}
} else {
struct buf **bpp, **endbp;
struct cluster_save *buflist;
buflist = cluster_collectbufs(cc, vp,
bp, blksize);
endbp = &buflist->bs_children
[buflist->bs_nchildren - 1];
if (VOP_REALLOCBLKS(vp, buflist)) {
for (bpp = buflist->bs_children;
bpp < endbp; bpp++)
brelse(*bpp);
kfree(buflist, M_SEGMENT);
if (seqcount > 1) {
cluster_wbuild_wb(vp,
blksize, cc->v_cstart,
cursize);
}
} else {
for (bpp = buflist->bs_children;
bpp <= endbp; bpp++)
bdwrite(*bpp);
kfree(buflist, M_SEGMENT);
cc->v_lastw = loffset + blksize;
cc->v_lasta = bp->b_bio2.bio_offset +
blksize;
cluster_putcache(cc);
return;
}
}
}
if ((vp->v_type == VREG) &&
bp->b_loffset + blksize < filesize &&
(bp->b_bio2.bio_offset == NOOFFSET) &&
(VOP_BMAP(vp, loffset, &bp->b_bio2.bio_offset, &maxclen, NULL, BUF_CMD_WRITE) ||
bp->b_bio2.bio_offset == NOOFFSET)) {
bdwrite(bp);
cc->v_clen = 0;
cc->v_lasta = bp->b_bio2.bio_offset + blksize;
cc->v_cstart = loffset;
cc->v_lastw = loffset + blksize;
cluster_putcache(cc);
return;
}
if (maxclen > blksize)
cc->v_clen = maxclen;
else
cc->v_clen = blksize;
if (!async && cc->v_clen == 0) {
cc->v_cstart = loffset;
bdwrite(bp);
} else {
cc->v_cstart = loffset;
bdwrite(bp);
}
} else if (loffset == cc->v_cstart + cc->v_clen) {
bdwrite(bp);
if (seqcount > 1)
cluster_wbuild_wb(vp, blksize, cc->v_cstart,
cc->v_clen + blksize);
cc->v_clen = 0;
cc->v_cstart = loffset;
} else if (vm_paging_severe() &&
bp->b_loffset + blksize < filesize) {
bawrite(bp);
} else {
bdwrite(bp);
}
cc->v_lastw = loffset + blksize;
cc->v_lasta = bp->b_bio2.bio_offset + blksize;
cluster_putcache(cc);
}
int
cluster_awrite(struct buf *bp)
{
int total;
if ((bp->b_flags & B_CLUSTEROK) == 0 ||
bp->b_vp == NULL ||
(bp->b_vp->v_flag & VOBJBUF) == 0) {
total = bp->b_bufsize;
bawrite(bp);
return (total);
}
total = cluster_wbuild(bp->b_vp, &bp, bp->b_bufsize,
bp->b_loffset, vmaxiosize(bp->b_vp));
if (bp)
bawrite(bp);
return total;
}
static int
cluster_wbuild(struct vnode *vp, struct buf **bpp,
int blksize, off_t start_loffset, int bytes)
{
struct buf *bp, *tbp;
int i, j;
int totalwritten = 0;
int must_initiate;
int maxiosize = vmaxiosize(vp);
while (bytes > 0) {
if (bpp && (*bpp)->b_loffset == start_loffset) {
tbp = *bpp;
*bpp = NULL;
bpp = NULL;
} else {
tbp = findblk(vp, start_loffset, FINDBLK_NBLOCK |
FINDBLK_KVABIO);
if (tbp == NULL ||
(tbp->b_flags & (B_LOCKED | B_INVAL | B_DELWRI)) !=
B_DELWRI ||
(LIST_FIRST(&tbp->b_dep) && buf_checkwrite(tbp))) {
if (tbp)
BUF_UNLOCK(tbp);
start_loffset += blksize;
bytes -= blksize;
continue;
}
bremfree(tbp);
}
KKASSERT(tbp->b_cmd == BUF_CMD_DONE);
if ((tbp->b_flags & B_CLUSTEROK) == 0 ||
tbp->b_bcount != tbp->b_bufsize ||
tbp->b_bcount != blksize ||
bytes == blksize) {
totalwritten += tbp->b_bufsize;
bawrite(tbp);
start_loffset += blksize;
bytes -= blksize;
continue;
}
if (vp->v_type == VCHR || vp->v_type == VBLK)
bp = getpbuf_kva(&vp->v_pbuf_count);
else
bp = getpbuf_kva(&vp->v_mount->mnt_pbuf_count);
bp->b_bcount = 0;
bp->b_bufsize = 0;
bp->b_xio.xio_npages = 0;
bp->b_loffset = tbp->b_loffset;
bp->b_bio2.bio_offset = tbp->b_bio2.bio_offset;
bp->b_vp = vp;
bp->b_data = (char *)((vm_offset_t)bp->b_data |
((vm_offset_t)tbp->b_data & PAGE_MASK));
bp->b_flags &= ~(B_ERROR | B_NOTMETA);
bp->b_flags |= B_CLUSTER | B_BNOCLIP | B_KVABIO |
(tbp->b_flags & (B_VMIO | B_NEEDCOMMIT |
B_NOTMETA));
bp->b_bio1.bio_caller_info1.cluster_head = NULL;
bp->b_bio1.bio_caller_info2.cluster_tail = NULL;
for (i = 0; i < bytes; (i += blksize), (start_loffset += blksize)) {
if (i == 0) {
must_initiate = 1;
} else {
must_initiate = 0;
tbp = findblk(vp, start_loffset,
FINDBLK_NBLOCK | FINDBLK_KVABIO);
if (tbp == NULL)
break;
if ((tbp->b_flags & (B_VMIO | B_CLUSTEROK |
B_INVAL | B_DELWRI | B_NEEDCOMMIT))
!= (B_DELWRI | B_CLUSTEROK |
(bp->b_flags & (B_VMIO | B_NEEDCOMMIT))) ||
(tbp->b_flags & B_LOCKED)
) {
BUF_UNLOCK(tbp);
break;
}
if ((tbp->b_bcount != blksize) ||
((bp->b_bio2.bio_offset + i) !=
tbp->b_bio2.bio_offset) ||
((tbp->b_xio.xio_npages + bp->b_xio.xio_npages) >
(maxiosize / PAGE_SIZE)) ||
(LIST_FIRST(&tbp->b_dep) &&
buf_checkwrite(tbp))
) {
BUF_UNLOCK(tbp);
break;
}
if (LIST_FIRST(&tbp->b_dep))
must_initiate = 1;
bremfree(tbp);
KKASSERT(tbp->b_cmd == BUF_CMD_DONE);
}
if (tbp->b_flags & B_VMIO) {
vm_page_t m;
if (must_initiate == 0) {
for (j = 0;
j < tbp->b_xio.xio_npages;
++j) {
m = tbp->b_xio.xio_pages[j];
if (m->busy_count &
PBUSY_LOCKED) {
bqrelse(tbp);
goto finishcluster;
}
}
}
for (j = 0; j < tbp->b_xio.xio_npages; ++j) {
m = tbp->b_xio.xio_pages[j];
vm_page_busy_wait(m, FALSE, "clurpg");
vm_page_io_start(m);
vm_page_wakeup(m);
vm_object_pip_add(m->object, 1);
if ((bp->b_xio.xio_npages == 0) ||
(bp->b_xio.xio_pages[bp->b_xio.xio_npages - 1] != m)) {
bp->b_xio.xio_pages[bp->b_xio.xio_npages] = m;
bp->b_xio.xio_npages++;
}
}
}
bp->b_bcount += blksize;
bp->b_bufsize += blksize;
tbp->b_flags &= ~B_ERROR;
tbp->b_cmd = BUF_CMD_WRITE;
BUF_KERNPROC(tbp);
cluster_append(&bp->b_bio1, tbp);
if (LIST_FIRST(&tbp->b_dep) != NULL)
buf_start(tbp);
}
finishcluster:
pmap_qenter_noinval(trunc_page((vm_offset_t)bp->b_data),
(vm_page_t *)bp->b_xio.xio_pages,
bp->b_xio.xio_npages);
if (bp->b_bufsize > bp->b_kvasize) {
panic("cluster_wbuild: b_bufsize(%d) "
"> b_kvasize(%d)\n",
bp->b_bufsize, bp->b_kvasize);
}
totalwritten += bp->b_bufsize;
bp->b_dirtyoff = 0;
bp->b_dirtyend = bp->b_bufsize;
bp->b_bio1.bio_done = cluster_callback;
bp->b_cmd = BUF_CMD_WRITE;
vfs_busy_pages(vp, bp);
bsetrunningbufspace(bp, bp->b_bufsize);
BUF_KERNPROC(bp);
vn_strategy(vp, &bp->b_bio1);
bytes -= i;
}
return totalwritten;
}
static struct cluster_save *
cluster_collectbufs(cluster_cache_t *cc, struct vnode *vp,
struct buf *last_bp, int blksize)
{
struct cluster_save *buflist;
struct buf *bp;
off_t loffset;
int i, len;
int j;
int k;
len = (int)(cc->v_lastw - cc->v_cstart) / blksize;
KKASSERT(len > 0);
buflist = kmalloc(sizeof(struct buf *) * (len + 1) + sizeof(*buflist),
M_SEGMENT, M_WAITOK);
buflist->bs_nchildren = 0;
buflist->bs_children = (struct buf **) (buflist + 1);
for (loffset = cc->v_cstart, i = 0, j = 0;
i < len;
(loffset += blksize), i++) {
bp = getcacheblk(vp, loffset,
last_bp->b_bcount, GETBLK_SZMATCH |
GETBLK_NOWAIT);
buflist->bs_children[i] = bp;
if (bp == NULL) {
j = i + 1;
} else if (bp->b_bio2.bio_offset == NOOFFSET) {
VOP_BMAP(bp->b_vp, bp->b_loffset,
&bp->b_bio2.bio_offset,
NULL, NULL, BUF_CMD_WRITE);
}
}
for (k = 0; k < j; ++k) {
if (buflist->bs_children[k]) {
bqrelse(buflist->bs_children[k]);
buflist->bs_children[k] = NULL;
}
}
if (j != 0) {
if (j != i) {
bcopy(buflist->bs_children + j,
buflist->bs_children + 0,
sizeof(buflist->bs_children[0]) * (i - j));
}
i -= j;
}
buflist->bs_children[i] = bp = last_bp;
if (bp->b_bio2.bio_offset == NOOFFSET) {
VOP_BMAP(bp->b_vp, bp->b_loffset, &bp->b_bio2.bio_offset,
NULL, NULL, BUF_CMD_WRITE);
}
buflist->bs_nchildren = i + 1;
return (buflist);
}
void
cluster_append(struct bio *bio, struct buf *tbp)
{
tbp->b_cluster_next = NULL;
if (bio->bio_caller_info1.cluster_head == NULL) {
bio->bio_caller_info1.cluster_head = tbp;
bio->bio_caller_info2.cluster_tail = tbp;
} else {
bio->bio_caller_info2.cluster_tail->b_cluster_next = tbp;
bio->bio_caller_info2.cluster_tail = tbp;
}
}
static
void
cluster_setram(struct buf *bp)
{
bp->b_flags |= B_RAM;
if (bp->b_xio.xio_npages)
vm_page_flag_set(bp->b_xio.xio_pages[0], PG_RAM);
}
static
void
cluster_clrram(struct buf *bp)
{
bp->b_flags &= ~B_RAM;
if (bp->b_xio.xio_npages)
vm_page_flag_clear(bp->b_xio.xio_pages[0], PG_RAM);
}