Index: sys/kern/kern_physio.c
===================================================================
RCS file: /cvsroot/src/sys/kern/kern_physio.c,v
retrieving revision 1.93
diff -p -u -r1.93 kern_physio.c
--- sys/kern/kern_physio.c      21 Apr 2015 10:54:52 -0000      1.93
+++ sys/kern/kern_physio.c      23 Mar 2019 10:21:19 -0000
@@ -75,6 +75,7 @@ __KERNEL_RCSID(0, "$NetBSD: kern_physio.

#include <sys/param.h>
#include <sys/systm.h>
+#include <sys/conf.h>
#include <sys/buf.h>
#include <sys/proc.h>
#include <sys/once.h>
@@ -100,6 +101,7 @@ struct physio_stat {
       int ps_error;
       int ps_failed;
       off_t ps_endoffset;
+       size_t ps_resid;
       buf_t *ps_orig_bp;
       kmutex_t ps_lock;
       kcondvar_t ps_cv;
@@ -152,6 +154,8 @@ physio_done(struct work *wk, void *dummy
                       ps->ps_error = bp->b_error;
               }
               ps->ps_failed++;
+
+               ps->ps_resid += todo - done;
       } else {
               KASSERT(bp->b_error == 0);
       }
@@ -220,6 +224,7 @@ physio(void (*strategy)(struct buf *), s
       struct buf *bp = NULL;
       struct physio_stat *ps;
       int concurrency = physio_concurrency - 1;
+       int isdisk;

       error = RUN_ONCE(&physio_initialized, physio_init);
       if (__predict_false(error != 0)) {
@@ -237,9 +242,15 @@ physio(void (*strategy)(struct buf *), s
       /* ps->ps_failed = 0; */
       ps->ps_orig_bp = obp;
       ps->ps_endoffset = -1;
+       ps->ps_resid = 0;
       mutex_init(&ps->ps_lock, MUTEX_DEFAULT, IPL_NONE);
       cv_init(&ps->ps_cv, "physio");

+       /* Allow concurrent I/O only for disks */
+       isdisk = cdev_type(dev) == D_DISK;
+       if (!isdisk)
+               concurrency = 0;
+
       /* Make sure we have a buffer, creating one if necessary. */
       if (obp != NULL) {
               mutex_enter(&bufcache_lock);
@@ -291,11 +302,30 @@ physio(void (*strategy)(struct buf *), s

                       /* Set up the buffer for a maximum-sized transfer. */
                       bp->b_blkno = btodb(uio->uio_offset);
-                       if (dbtob(bp->b_blkno) != uio->uio_offset) {
-                               error = EINVAL;
-                               goto done;
+                       if (isdisk) {
+                               /*
+                                * For disks, check that offsets are at least block
+                                * aligned, the block addresses are used to track
+                                * errors of finished requests.
+                                */
+                               if (dbtob(bp->b_blkno) != uio->uio_offset) {
+                                       error = EINVAL;
+                                       goto done;
+                               }
+                               /*
+                                * Split request into MAXPHYS chunks
+                                */
+                               bp->b_bcount = MIN(MAXPHYS, iovp->iov_len);
+                       } else {
+                               /*
+                                * Verify that buffer can handle size
+                                */
+                               if (iovp->iov_len > MAXBSIZE) {
+                                       error = EINVAL;
+                                       goto done;
+                               }
+                               bp->b_bcount = iovp->iov_len;
                       }
-                       bp->b_bcount = MIN(MAXPHYS, iovp->iov_len);
                       bp->b_data = iovp->iov_base;

                       /*
@@ -364,16 +394,25 @@ done_locked:
       physio_wait(ps, 0);
       mutex_exit(&ps->ps_lock);

-       if (ps->ps_failed != 0) {
-               off_t delta;
+       KASSERT(ps->ps_failed || ps->ps_endoffset == -1);

-               delta = uio->uio_offset - ps->ps_endoffset;
-               KASSERT(delta > 0);
-               uio->uio_resid += delta;
-               /* uio->uio_offset = ps->ps_endoffset; */
+       /*
+        * Compute residual, for disks adjust for the
+        * lowest numbered block that returned an error.
+        */
+       if (isdisk) {
+               if (ps->ps_failed != 0) {
+                       off_t delta;
+
+                       delta = uio->uio_offset - ps->ps_endoffset;
+                       KASSERT(delta > 0);
+                       uio->uio_resid += delta;
+                       /* uio->uio_offset = ps->ps_endoffset; */
+               }
       } else {
-               KASSERT(ps->ps_endoffset == -1);
+               uio->uio_resid += ps->ps_resid;
       }
+
       if (bp != NULL && bp != obp) {
               putiobuf(bp);
       }