* [PATCH v7 0/4] scatterlist: add new capabilities
@ 2022-02-01 3:49 Douglas Gilbert
2022-02-01 3:49 ` [PATCH v7 1/4] sgl_alloc_order: remove 4 GiB limit Douglas Gilbert
` (3 more replies)
0 siblings, 4 replies; 7+ messages in thread
From: Douglas Gilbert @ 2022-02-01 3:49 UTC (permalink / raw)
To: linux-scsi, linux-block; +Cc: martin.petersen, jejb, hare, bvanassche
Scatter-gather lists (sgl_s) are frequently used as data carriers in
the block layer. For example the SCSI and NVMe subsystems interchange
data with the block layer using sgl_s. The sgl API is declared in
<linux/scatterlist.h>
This patchset extends the scatterlist API by adding functions to:
- copy a sgl to another sgl, stop after copying n_bytes or
when either sgl is exhausted [2/4]
- compare one sgl against another sgl for equality. Stop when
either sgl is exhausted, or a miscompare is detected or when
n_bytes are compared. Supply a variant function that gives the
position of the miscompare [3/4]
- generalize the existing sg_zero_buffer() function with a
new sgl_memset function [4/4]
The first patch [1/4] removes a 4 GiB size limitation from the
sgl_alloc_order() function.
The author changed the backing store (i.e. ramdisks) behind the
scsi_debug driver from using vmalloc() to using the scatterlist
API with the above additions. The removal of the 4 GiB size limit
allows scsi_debug to mimic a disk of larger size. Being able to
copy one sgl to another simplifies implementing SCSI READ and WRITE
commands. The sgl_equal_sgl() function both simplifies the SCSI
VERIFY(BytChk=1) and COMPARE AND WRITE commands and is a performance
win as there is no need for a temporary buffer to hold the data-out
transfer associated with these comparison commands.
The target subsystem and NVMe may find these additions to the
scatterlist API useful.
Changes since v6 [posted 20210118]:
- re-add sgl_alloc_order() fix to remove its (undocumented) 4 GiB
limit
- rebase on lk 5.17.0-rc1
Changes since v5 [posted 20201228]:
- incorporate review requests from Jason Gunthorpe
- replace integer overflow detection code in sgl_alloc_order()
with a pre-condition statement
- rebase on lk 5.11.0-rc4
Changes since v4 [posted 20201105]:
- rebase on lk 5.10.0-rc2
Changes since v3 [posted 20201019]:
- re-instate check on integer overflow of nent calculation in
sgl_alloc_order(). Do it in such a way as to not limit the
overall sgl size to 4 GiB
- introduce sgl_compare_sgl_idx() helper function that, if
requested and if a miscompare is detected, will yield the byte
index of the first miscompare.
- add Reviewed-by tags from Bodo Stroesser
- rebase on lk 5.10.0-rc2 [was on lk 5.9.0]
Changes since v2 [posted 20201018]:
- remove unneeded lines from sgl_memset() definition.
- change sg_zero_buffer() to call sgl_memset() as the former
is a subset.
Changes since v1 [posted 20201016]:
- Bodo Stroesser pointed out a problem with the nesting of
kmap_atomic() [called via sg_miter_next()] and kunmap_atomic()
calls [called via sg_miter_stop()] and proposed a solution that
simplifies the previous code.
- the new implementation of the three functions has shorter periods
when pre-emption is disabled (but has more them). This should
make operations on large sgl_s more pre-emption "friendly" with
a relatively small performance hit.
- sgl_memset return type changed from void to size_t and is the
number of bytes actually (over)written. That number is needed
anyway internally so may as well return it as it may be useful to
the caller.
This patchset is against lk 5.17.0-rc1
*** BLURB HERE ***
Douglas Gilbert (4):
sgl_alloc_order: remove 4 GiB limit
scatterlist: add sgl_copy_sgl() function
scatterlist: add sgl_equal_sgl() function
scatterlist: add sgl_memset()
include/linux/scatterlist.h | 33 ++++-
lib/scatterlist.c | 256 +++++++++++++++++++++++++++++++-----
2 files changed, 256 insertions(+), 33 deletions(-)
--
2.25.1
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v7 1/4] sgl_alloc_order: remove 4 GiB limit
2022-02-01 3:49 [PATCH v7 0/4] scatterlist: add new capabilities Douglas Gilbert
@ 2022-02-01 3:49 ` Douglas Gilbert
2022-02-01 12:36 ` Jason Gunthorpe
2022-02-01 13:46 ` kernel test robot
2022-02-01 3:49 ` [PATCH v7 2/4] scatterlist: add sgl_copy_sgl() function Douglas Gilbert
` (2 subsequent siblings)
3 siblings, 2 replies; 7+ messages in thread
From: Douglas Gilbert @ 2022-02-01 3:49 UTC (permalink / raw)
To: linux-scsi, linux-block
Cc: martin.petersen, jejb, hare, bvanassche, Jason Gunthorpe,
Bodo Stroesser
This patch fixes a check done by sgl_alloc_order() before it starts
any allocations. The comment in the original said: "Check for integer
overflow" but the right hand side of the expression in the condition
is resolved as u32 so it can not exceed UINT32_MAX (4 GiB) which
means 'length' can not exceed that value.
This function may be used to replace vmalloc(unsigned long) for a
large allocation (e.g. a ramdisk). vmalloc has no limit at 4 GiB so
it seems unreasonable that sgl_alloc_order() whose length type is
unsigned long long should be limited to 4 GB.
In early 2021 there was discussion between Jason Gunthorpe
<jgg@ziepe.ca> and Bodo Stroesser <bostroesser@gmail.com> about the
way to check for overflow caused by order (an exponent) being
too large. Take the solution proposed by Bodo in post dated
20210118 to the linux-scsi and linux-block lists.
An earlier patch fixed a memory leak in sg_alloc_order() due to the
misuse of sgl_free(). Take the opportunity to put a one line comment
above sgl_free()'s declaration warning that it is not suitable when
order > 0 .
Cc: Jason Gunthorpe <jgg@ziepe.ca>
Reviewed-by: Bodo Stroesser <bostroesser@gmail.com>
Signed-off-by: Douglas Gilbert <dgilbert@interlog.com>
---
include/linux/scatterlist.h | 1 +
lib/scatterlist.c | 24 +++++++++++++-----------
2 files changed, 14 insertions(+), 11 deletions(-)
diff --git a/include/linux/scatterlist.h b/include/linux/scatterlist.h
index 7ff9d6386c12..03130be581bb 100644
--- a/include/linux/scatterlist.h
+++ b/include/linux/scatterlist.h
@@ -357,6 +357,7 @@ struct scatterlist *sgl_alloc(unsigned long long length, gfp_t gfp,
unsigned int *nent_p);
void sgl_free_n_order(struct scatterlist *sgl, int nents, int order);
void sgl_free_order(struct scatterlist *sgl, int order);
+/* Only use sgl_free() when order is 0 */
void sgl_free(struct scatterlist *sgl);
#endif /* CONFIG_SGL_ALLOC */
diff --git a/lib/scatterlist.c b/lib/scatterlist.c
index d5e82e4a57ad..ed6d0465c78e 100644
--- a/lib/scatterlist.c
+++ b/lib/scatterlist.c
@@ -585,13 +585,16 @@ EXPORT_SYMBOL(sg_alloc_table_from_pages_segment);
#ifdef CONFIG_SGL_ALLOC
/**
- * sgl_alloc_order - allocate a scatterlist and its pages
+ * sgl_alloc_order - allocate a scatterlist with equally sized elements each
+ * of which has 2^@order continuous pages
* @length: Length in bytes of the scatterlist. Must be at least one
- * @order: Second argument for alloc_pages()
+ * @order: Second argument for alloc_pages(). Each sgl element size will
+ * be (PAGE_SIZE*2^@order) bytes. @order must not exceed 16.
* @chainable: Whether or not to allocate an extra element in the scatterlist
- * for scatterlist chaining purposes
+ * for scatterlist chaining purposes
* @gfp: Memory allocation flags
- * @nent_p: [out] Number of entries in the scatterlist that have pages
+ * @nent_p: [out] Number of entries in the scatterlist that have pages.
+ * Ignored if @nent_p is NULL.
*
* Returns: A pointer to an initialized scatterlist or %NULL upon failure.
*/
@@ -604,16 +607,15 @@ struct scatterlist *sgl_alloc_order(unsigned long long length,
unsigned int nent, nalloc;
u32 elem_len;
- nent = round_up(length, PAGE_SIZE << order) >> (PAGE_SHIFT + order);
- /* Check for integer overflow */
- if (length > (nent << (PAGE_SHIFT + order)))
+ if (length >> (PAGE_SHIFT + order) >= UINT_MAX)
return NULL;
- nalloc = nent;
+ nent = DIV_ROUND_UP(length, PAGE_SIZE << order);
+
if (chainable) {
- /* Check for integer overflow */
- if (nalloc + 1 < nalloc)
+ if (check_add_overflow(nent, 1U, &nalloc))
return NULL;
- nalloc++;
+ } else {
+ nalloc = nent;
}
sgl = kmalloc_array(nalloc, sizeof(struct scatterlist),
gfp & ~GFP_DMA);
--
2.25.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v7 2/4] scatterlist: add sgl_copy_sgl() function
2022-02-01 3:49 [PATCH v7 0/4] scatterlist: add new capabilities Douglas Gilbert
2022-02-01 3:49 ` [PATCH v7 1/4] sgl_alloc_order: remove 4 GiB limit Douglas Gilbert
@ 2022-02-01 3:49 ` Douglas Gilbert
2022-02-01 3:49 ` [PATCH v7 3/4] scatterlist: add sgl_equal_sgl() function Douglas Gilbert
2022-02-01 3:49 ` [PATCH v7 4/4] scatterlist: add sgl_memset() Douglas Gilbert
3 siblings, 0 replies; 7+ messages in thread
From: Douglas Gilbert @ 2022-02-01 3:49 UTC (permalink / raw)
To: linux-scsi, linux-block
Cc: martin.petersen, jejb, hare, bvanassche, Bodo Stroesser
Both the SCSI and NVMe subsystems receive user data from the block
layer in scatterlist_s (aka scatter gather lists (sgl) which are
often arrays). If drivers in those subsystems represent storage
(e.g. a ramdisk) or cache "hot" user data then they may also
choose to use scatterlist_s. Currently there are no sgl to sgl
operations in the kernel. Start with a sgl to sgl copy. Stops
when the first of the number of requested bytes to copy, or the
source sgl, or the destination sgl is exhausted. So the
destination sgl will _not_ grow.
Reviewed-by: Bodo Stroesser <bostroesser@gmail.com>
Signed-off-by: Douglas Gilbert <dgilbert@interlog.com>
---
include/linux/scatterlist.h | 4 ++
lib/scatterlist.c | 74 +++++++++++++++++++++++++++++++++++++
2 files changed, 78 insertions(+)
diff --git a/include/linux/scatterlist.h b/include/linux/scatterlist.h
index 03130be581bb..1291ff63fe83 100644
--- a/include/linux/scatterlist.h
+++ b/include/linux/scatterlist.h
@@ -376,6 +376,10 @@ size_t sg_pcopy_to_buffer(struct scatterlist *sgl, unsigned int nents,
size_t sg_zero_buffer(struct scatterlist *sgl, unsigned int nents,
size_t buflen, off_t skip);
+size_t sgl_copy_sgl(struct scatterlist *d_sgl, unsigned int d_nents, off_t d_skip,
+ struct scatterlist *s_sgl, unsigned int s_nents, off_t s_skip,
+ size_t n_bytes);
+
/*
* Maximum number of entries that will be allocated in one piece, if
* a list larger than this is required then chaining will be utilized.
diff --git a/lib/scatterlist.c b/lib/scatterlist.c
index ed6d0465c78e..d4d56eece250 100644
--- a/lib/scatterlist.c
+++ b/lib/scatterlist.c
@@ -1087,3 +1087,77 @@ size_t sg_zero_buffer(struct scatterlist *sgl, unsigned int nents,
return offset;
}
EXPORT_SYMBOL(sg_zero_buffer);
+
+/**
+ * sgl_copy_sgl - Copy over a destination sgl from a source sgl
+ * @d_sgl: Destination sgl
+ * @d_nents: Number of SG entries in destination sgl
+ * @d_skip: Number of bytes to skip in destination before starting
+ * @s_sgl: Source sgl
+ * @s_nents: Number of SG entries in source sgl
+ * @s_skip: Number of bytes to skip in source before starting
+ * @n_bytes: The (maximum) number of bytes to copy
+ *
+ * Returns:
+ * The number of copied bytes.
+ *
+ * Notes:
+ * Destination arguments appear before the source arguments, as with memcpy().
+ *
+ * Stops copying if either d_sgl, s_sgl or n_bytes is exhausted.
+ *
+ * Since memcpy() is used, overlapping copies (where d_sgl and s_sgl belong
+ * to the same sgl and the copy regions overlap) are not supported.
+ *
+ * Large copies are broken into copy segments whose sizes may vary. Those
+ * copy segment sizes are chosen by the min3() statement in the code below.
+ * Since SG_MITER_ATOMIC is used for both sides, each copy segment is started
+ * with kmap_atomic() [in sg_miter_next()] and completed with kunmap_atomic()
+ * [in sg_miter_stop()]. This means pre-emption is inhibited for relatively
+ * short periods even in very large copies.
+ *
+ * If d_skip is large, potentially spanning multiple d_nents then some
+ * integer arithmetic to adjust d_sgl may improve performance. For example
+ * if d_sgl is built using sgl_alloc_order(chainable=false) then the sgl
+ * will be an array with equally sized segments facilitating that
+ * arithmetic. The suggestion applies to s_skip, s_sgl and s_nents as well.
+ *
+ **/
+size_t sgl_copy_sgl(struct scatterlist *d_sgl, unsigned int d_nents, off_t d_skip,
+ struct scatterlist *s_sgl, unsigned int s_nents, off_t s_skip,
+ size_t n_bytes)
+{
+ size_t len;
+ size_t offset = 0;
+ struct sg_mapping_iter d_iter, s_iter;
+
+ if (n_bytes == 0)
+ return 0;
+ sg_miter_start(&s_iter, s_sgl, s_nents, SG_MITER_ATOMIC | SG_MITER_FROM_SG);
+ sg_miter_start(&d_iter, d_sgl, d_nents, SG_MITER_ATOMIC | SG_MITER_TO_SG);
+ if (!sg_miter_skip(&s_iter, s_skip))
+ goto fini;
+ if (!sg_miter_skip(&d_iter, d_skip))
+ goto fini;
+
+ while (offset < n_bytes) {
+ if (!sg_miter_next(&s_iter))
+ break;
+ if (!sg_miter_next(&d_iter))
+ break;
+ len = min3(d_iter.length, s_iter.length, n_bytes - offset);
+
+ memcpy(d_iter.addr, s_iter.addr, len);
+ offset += len;
+ /* LIFO order (stop d_iter before s_iter) needed with SG_MITER_ATOMIC */
+ d_iter.consumed = len;
+ sg_miter_stop(&d_iter);
+ s_iter.consumed = len;
+ sg_miter_stop(&s_iter);
+ }
+fini:
+ sg_miter_stop(&d_iter);
+ sg_miter_stop(&s_iter);
+ return offset;
+}
+EXPORT_SYMBOL(sgl_copy_sgl);
--
2.25.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v7 3/4] scatterlist: add sgl_equal_sgl() function
2022-02-01 3:49 [PATCH v7 0/4] scatterlist: add new capabilities Douglas Gilbert
2022-02-01 3:49 ` [PATCH v7 1/4] sgl_alloc_order: remove 4 GiB limit Douglas Gilbert
2022-02-01 3:49 ` [PATCH v7 2/4] scatterlist: add sgl_copy_sgl() function Douglas Gilbert
@ 2022-02-01 3:49 ` Douglas Gilbert
2022-02-01 3:49 ` [PATCH v7 4/4] scatterlist: add sgl_memset() Douglas Gilbert
3 siblings, 0 replies; 7+ messages in thread
From: Douglas Gilbert @ 2022-02-01 3:49 UTC (permalink / raw)
To: linux-scsi, linux-block
Cc: martin.petersen, jejb, hare, bvanassche, Bodo Stroesser,
David Disseldorp
After enabling copies between scatter gather lists (sgl_s), another
storage related operation is to compare two sgl_s for equality. This
new function is designed to partially implement NVMe's Compare
command and the SCSI VERIFY(BYTCHK=1) command. Like memcmp() this
function begins scanning at the start (of each sgl) and returns
false on the first miscompare and stops comparing.
The sgl_equal_sgl_idx() function additionally yields the index (i.e.
byte position) of the first miscompare. The additional parameter,
miscompare_idx, is a pointer. If it is non-NULL and a miscompare is
detected (i.e. the function returns false) then the byte index of
the first miscompare is written to *miscompare_idx. Knowing the
location of the first miscompare is needed to implement properly
the SCSI COMPARE AND WRITE command.
Reviewed-by: Bodo Stroesser <bostroesser@gmail.com>
Reviewed-by: David Disseldorp <ddiss@suse.de>
Signed-off-by: Douglas Gilbert <dgilbert@interlog.com>
---
include/linux/scatterlist.h | 8 +++
lib/scatterlist.c | 110 ++++++++++++++++++++++++++++++++++++
2 files changed, 118 insertions(+)
diff --git a/include/linux/scatterlist.h b/include/linux/scatterlist.h
index 1291ff63fe83..a96874a8b791 100644
--- a/include/linux/scatterlist.h
+++ b/include/linux/scatterlist.h
@@ -380,6 +380,14 @@ size_t sgl_copy_sgl(struct scatterlist *d_sgl, unsigned int d_nents, off_t d_ski
struct scatterlist *s_sgl, unsigned int s_nents, off_t s_skip,
size_t n_bytes);
+bool sgl_equal_sgl(struct scatterlist *x_sgl, unsigned int x_nents, off_t x_skip,
+ struct scatterlist *y_sgl, unsigned int y_nents, off_t y_skip,
+ size_t n_bytes);
+
+bool sgl_equal_sgl_idx(struct scatterlist *x_sgl, unsigned int x_nents, off_t x_skip,
+ struct scatterlist *y_sgl, unsigned int y_nents, off_t y_skip,
+ size_t n_bytes, size_t *miscompare_idx);
+
/*
* Maximum number of entries that will be allocated in one piece, if
* a list larger than this is required then chaining will be utilized.
diff --git a/lib/scatterlist.c b/lib/scatterlist.c
index d4d56eece250..1fb14b2e320a 100644
--- a/lib/scatterlist.c
+++ b/lib/scatterlist.c
@@ -1161,3 +1161,113 @@ size_t sgl_copy_sgl(struct scatterlist *d_sgl, unsigned int d_nents, off_t d_ski
return offset;
}
EXPORT_SYMBOL(sgl_copy_sgl);
+
+/**
+ * sgl_equal_sgl_idx - check if x and y (both sgl_s) compare equal, report
+ * index for first unequal bytes
+ * @x_sgl: x (left) sgl
+ * @x_nents: Number of SG entries in x (left) sgl
+ * @x_skip: Number of bytes to skip in x (left) before starting
+ * @y_sgl: y (right) sgl
+ * @y_nents: Number of SG entries in y (right) sgl
+ * @y_skip: Number of bytes to skip in y (right) before starting
+ * @n_bytes: The (maximum) number of bytes to compare
+ * @miscompare_idx: if return is false, index of first miscompare written
+ * to this pointer (if non-NULL). Value will be < n_bytes
+ *
+ * Returns:
+ * true if x and y compare equal before x, y or n_bytes is exhausted.
+ * Otherwise on a miscompare, returns false (and stops comparing). If return
+ * is false and miscompare_idx is non-NULL, then index of first miscompared
+ * byte written to *miscompare_idx.
+ *
+ * Notes:
+ * x and y are symmetrical: they can be swapped and the result is the same.
+ *
+ * Implementation is based on memcmp(). x and y segments may overlap.
+ *
+ * The notes in sgl_copy_sgl() about large sgl_s _applies here as well.
+ *
+ **/
+bool sgl_equal_sgl_idx(struct scatterlist *x_sgl, unsigned int x_nents, off_t x_skip,
+ struct scatterlist *y_sgl, unsigned int y_nents, off_t y_skip,
+ size_t n_bytes, size_t *miscompare_idx)
+{
+ bool equ = true;
+ size_t len;
+ size_t offset = 0;
+ struct sg_mapping_iter x_iter, y_iter;
+
+ if (n_bytes == 0)
+ return true;
+ sg_miter_start(&x_iter, x_sgl, x_nents, SG_MITER_ATOMIC | SG_MITER_FROM_SG);
+ sg_miter_start(&y_iter, y_sgl, y_nents, SG_MITER_ATOMIC | SG_MITER_FROM_SG);
+ if (!sg_miter_skip(&x_iter, x_skip))
+ goto fini;
+ if (!sg_miter_skip(&y_iter, y_skip))
+ goto fini;
+
+ while (offset < n_bytes) {
+ if (!sg_miter_next(&x_iter))
+ break;
+ if (!sg_miter_next(&y_iter))
+ break;
+ len = min3(x_iter.length, y_iter.length, n_bytes - offset);
+
+ equ = !memcmp(x_iter.addr, y_iter.addr, len);
+ if (!equ)
+ goto fini;
+ offset += len;
+ /* LIFO order is important when SG_MITER_ATOMIC is used */
+ y_iter.consumed = len;
+ sg_miter_stop(&y_iter);
+ x_iter.consumed = len;
+ sg_miter_stop(&x_iter);
+ }
+fini:
+ if (miscompare_idx && !equ) {
+ u8 *xp = x_iter.addr;
+ u8 *yp = y_iter.addr;
+ u8 *x_endp;
+
+ for (x_endp = xp + len ; xp < x_endp; ++xp, ++yp) {
+ if (*xp != *yp)
+ break;
+ }
+ *miscompare_idx = offset + len - (x_endp - xp);
+ }
+ sg_miter_stop(&y_iter);
+ sg_miter_stop(&x_iter);
+ return equ;
+}
+EXPORT_SYMBOL(sgl_equal_sgl_idx);
+
+/**
+ * sgl_equal_sgl - check if x and y (both sgl_s) compare equal
+ * @x_sgl: x (left) sgl
+ * @x_nents: Number of SG entries in x (left) sgl
+ * @x_skip: Number of bytes to skip in x (left) before starting
+ * @y_sgl: y (right) sgl
+ * @y_nents: Number of SG entries in y (right) sgl
+ * @y_skip: Number of bytes to skip in y (right) before starting
+ * @n_bytes: The (maximum) number of bytes to compare
+ *
+ * Returns:
+ * true if x and y compare equal before x, y or n_bytes is exhausted.
+ * Otherwise on a miscompare, returns false (and stops comparing).
+ *
+ * Notes:
+ * x and y are symmetrical: they can be swapped and the result is the same.
+ *
+ * Implementation is based on memcmp(). x and y segments may overlap.
+ *
+ * The notes in sgl_copy_sgl() about large sgl_s _applies here as well.
+ *
+ **/
+bool sgl_equal_sgl(struct scatterlist *x_sgl, unsigned int x_nents, off_t x_skip,
+ struct scatterlist *y_sgl, unsigned int y_nents, off_t y_skip,
+ size_t n_bytes)
+{
+ return sgl_equal_sgl_idx(x_sgl, x_nents, x_skip, y_sgl, y_nents, y_skip, n_bytes, NULL);
+}
+EXPORT_SYMBOL(sgl_equal_sgl);
--
2.25.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v7 4/4] scatterlist: add sgl_memset()
2022-02-01 3:49 [PATCH v7 0/4] scatterlist: add new capabilities Douglas Gilbert
` (2 preceding siblings ...)
2022-02-01 3:49 ` [PATCH v7 3/4] scatterlist: add sgl_equal_sgl() function Douglas Gilbert
@ 2022-02-01 3:49 ` Douglas Gilbert
3 siblings, 0 replies; 7+ messages in thread
From: Douglas Gilbert @ 2022-02-01 3:49 UTC (permalink / raw)
To: linux-scsi, linux-block
Cc: martin.petersen, jejb, hare, bvanassche, Bodo Stroesser
The existing sg_zero_buffer() function is a bit restrictive. For
example protection information (PI) blocks are usually initialized
to 0xff bytes. As its name suggests sgl_memset() is modelled on
memset(). One difference is the type of the val argument which is
u8 rather than int. Plus it returns the number of bytes (over)written.
Change implementation of sg_zero_buffer() to call this new function.
Reviewed-by: Bodo Stroesser <bostroesser@gmail.com>
Signed-off-by: Douglas Gilbert <dgilbert@interlog.com>
---
include/linux/scatterlist.h | 20 +++++++++-
lib/scatterlist.c | 78 ++++++++++++++++++++-----------------
2 files changed, 61 insertions(+), 37 deletions(-)
diff --git a/include/linux/scatterlist.h b/include/linux/scatterlist.h
index a96874a8b791..c0b7f5938e64 100644
--- a/include/linux/scatterlist.h
+++ b/include/linux/scatterlist.h
@@ -373,8 +373,6 @@ size_t sg_pcopy_from_buffer(struct scatterlist *sgl, unsigned int nents,
const void *buf, size_t buflen, off_t skip);
size_t sg_pcopy_to_buffer(struct scatterlist *sgl, unsigned int nents,
void *buf, size_t buflen, off_t skip);
-size_t sg_zero_buffer(struct scatterlist *sgl, unsigned int nents,
- size_t buflen, off_t skip);
size_t sgl_copy_sgl(struct scatterlist *d_sgl, unsigned int d_nents, off_t d_skip,
struct scatterlist *s_sgl, unsigned int s_nents, off_t s_skip,
@@ -388,6 +386,24 @@ bool sgl_equal_sgl_idx(struct scatterlist *x_sgl, unsigned int x_nents, off_t x_
struct scatterlist *y_sgl, unsigned int y_nents, off_t y_skip,
size_t n_bytes, size_t *miscompare_idx);
+size_t sgl_memset(struct scatterlist *sgl, unsigned int nents, off_t skip,
+ u8 val, size_t n_bytes);
+
+/**
+ * sg_zero_buffer - Zero-out a part of a SG list
+ * @sgl: The SG list
+ * @nents: Number of SG entries
+ * @buflen: The number of bytes to zero out
+ * @skip: Number of bytes to skip before zeroing
+ *
+ * Returns the number of bytes zeroed.
+ **/
+static inline size_t sg_zero_buffer(struct scatterlist *sgl, unsigned int nents,
+ size_t buflen, off_t skip)
+{
+ return sgl_memset(sgl, nents, skip, 0, buflen);
+}
+
/*
* Maximum number of entries that will be allocated in one piece, if
* a list larger than this is required then chaining will be utilized.
diff --git a/lib/scatterlist.c b/lib/scatterlist.c
index 1fb14b2e320a..35d706eed9c2 100644
--- a/lib/scatterlist.c
+++ b/lib/scatterlist.c
@@ -1053,41 +1053,6 @@ size_t sg_pcopy_to_buffer(struct scatterlist *sgl, unsigned int nents,
}
EXPORT_SYMBOL(sg_pcopy_to_buffer);
-/**
- * sg_zero_buffer - Zero-out a part of a SG list
- * @sgl: The SG list
- * @nents: Number of SG entries
- * @buflen: The number of bytes to zero out
- * @skip: Number of bytes to skip before zeroing
- *
- * Returns the number of bytes zeroed.
- **/
-size_t sg_zero_buffer(struct scatterlist *sgl, unsigned int nents,
- size_t buflen, off_t skip)
-{
- unsigned int offset = 0;
- struct sg_mapping_iter miter;
- unsigned int sg_flags = SG_MITER_ATOMIC | SG_MITER_TO_SG;
-
- sg_miter_start(&miter, sgl, nents, sg_flags);
-
- if (!sg_miter_skip(&miter, skip))
- return false;
-
- while (offset < buflen && sg_miter_next(&miter)) {
- unsigned int len;
-
- len = min(miter.length, buflen - offset);
- memset(miter.addr, 0, len);
-
- offset += len;
- }
-
- sg_miter_stop(&miter);
- return offset;
-}
-EXPORT_SYMBOL(sg_zero_buffer);
-
/**
* sgl_copy_sgl - Copy over a destination sgl from a source sgl
* @d_sgl: Destination sgl
@@ -1271,3 +1236,46 @@ bool sgl_equal_sgl(struct scatterlist *x_sgl, unsigned int x_nents, off_t x_skip
return sgl_equal_sgl_idx(x_sgl, x_nents, x_skip, y_sgl, y_nents, y_skip, n_bytes, NULL);
}
EXPORT_SYMBOL(sgl_equal_sgl);
+
+/**
+ * sgl_memset - set byte 'val' up to n_bytes times on SG list
+ * @sgl: The SG list
+ * @nents: Number of SG entries in sgl
+ * @skip: Number of bytes to skip before starting
+ * @val: byte value to write to sgl
+ * @n_bytes: The (maximum) number of bytes to modify
+ *
+ * Returns:
+ * The number of bytes written.
+ *
+ * Notes:
+ * Stops writing if either sgl or n_bytes is exhausted. If n_bytes is
+ * set SIZE_MAX then val will be written to each byte until the end
+ * of sgl.
+ *
+ * The notes in sgl_copy_sgl() about large sgl_s _applies here as well.
+ *
+ **/
+size_t sgl_memset(struct scatterlist *sgl, unsigned int nents, off_t skip,
+ u8 val, size_t n_bytes)
+{
+ size_t offset = 0;
+ size_t len;
+ struct sg_mapping_iter miter;
+
+ if (n_bytes == 0)
+ return 0;
+ sg_miter_start(&miter, sgl, nents, SG_MITER_ATOMIC | SG_MITER_TO_SG);
+ if (!sg_miter_skip(&miter, skip))
+ goto fini;
+
+ while ((offset < n_bytes) && sg_miter_next(&miter)) {
+ len = min(miter.length, n_bytes - offset);
+ memset(miter.addr, val, len);
+ offset += len;
+ }
+fini:
+ sg_miter_stop(&miter);
+ return offset;
+}
+EXPORT_SYMBOL(sgl_memset);
--
2.25.1
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v7 1/4] sgl_alloc_order: remove 4 GiB limit
2022-02-01 3:49 ` [PATCH v7 1/4] sgl_alloc_order: remove 4 GiB limit Douglas Gilbert
@ 2022-02-01 12:36 ` Jason Gunthorpe
2022-02-01 13:46 ` kernel test robot
1 sibling, 0 replies; 7+ messages in thread
From: Jason Gunthorpe @ 2022-02-01 12:36 UTC (permalink / raw)
To: Douglas Gilbert
Cc: linux-scsi, linux-block, martin.petersen, jejb, hare, bvanassche,
Bodo Stroesser
On Mon, Jan 31, 2022 at 10:49:12PM -0500, Douglas Gilbert wrote:
> This patch fixes a check done by sgl_alloc_order() before it starts
> any allocations. The comment in the original said: "Check for integer
> overflow" but the right hand side of the expression in the condition
> is resolved as u32 so it can not exceed UINT32_MAX (4 GiB) which
> means 'length' can not exceed that value.
>
> This function may be used to replace vmalloc(unsigned long) for a
> large allocation (e.g. a ramdisk). vmalloc has no limit at 4 GiB so
> it seems unreasonable that sgl_alloc_order() whose length type is
> unsigned long long should be limited to 4 GB.
>
> In early 2021 there was discussion between Jason Gunthorpe
> <jgg@ziepe.ca> and Bodo Stroesser <bostroesser@gmail.com> about the
> way to check for overflow caused by order (an exponent) being
> too large. Take the solution proposed by Bodo in post dated
> 20210118 to the linux-scsi and linux-block lists.
>
> An earlier patch fixed a memory leak in sg_alloc_order() due to the
> misuse of sgl_free(). Take the opportunity to put a one line comment
> above sgl_free()'s declaration warning that it is not suitable when
> order > 0 .
>
> Cc: Jason Gunthorpe <jgg@ziepe.ca>
> Reviewed-by: Bodo Stroesser <bostroesser@gmail.com>
> Signed-off-by: Douglas Gilbert <dgilbert@interlog.com>
> ---
> include/linux/scatterlist.h | 1 +
> lib/scatterlist.c | 24 +++++++++++++-----------
> 2 files changed, 14 insertions(+), 11 deletions(-)
>
> diff --git a/include/linux/scatterlist.h b/include/linux/scatterlist.h
> index 7ff9d6386c12..03130be581bb 100644
> --- a/include/linux/scatterlist.h
> +++ b/include/linux/scatterlist.h
> @@ -357,6 +357,7 @@ struct scatterlist *sgl_alloc(unsigned long long length, gfp_t gfp,
> unsigned int *nent_p);
> void sgl_free_n_order(struct scatterlist *sgl, int nents, int order);
> void sgl_free_order(struct scatterlist *sgl, int order);
> +/* Only use sgl_free() when order is 0 */
> void sgl_free(struct scatterlist *sgl);
> #endif /* CONFIG_SGL_ALLOC */
>
> diff --git a/lib/scatterlist.c b/lib/scatterlist.c
> index d5e82e4a57ad..ed6d0465c78e 100644
> --- a/lib/scatterlist.c
> +++ b/lib/scatterlist.c
> @@ -585,13 +585,16 @@ EXPORT_SYMBOL(sg_alloc_table_from_pages_segment);
> #ifdef CONFIG_SGL_ALLOC
>
> /**
> - * sgl_alloc_order - allocate a scatterlist and its pages
> + * sgl_alloc_order - allocate a scatterlist with equally sized elements each
> + * of which has 2^@order continuous pages
> * @length: Length in bytes of the scatterlist. Must be at least one
> - * @order: Second argument for alloc_pages()
> + * @order: Second argument for alloc_pages(). Each sgl element size will
> + * be (PAGE_SIZE*2^@order) bytes. @order must not exceed 16.
> * @chainable: Whether or not to allocate an extra element in the scatterlist
> - * for scatterlist chaining purposes
> + * for scatterlist chaining purposes
> * @gfp: Memory allocation flags
> - * @nent_p: [out] Number of entries in the scatterlist that have pages
> + * @nent_p: [out] Number of entries in the scatterlist that have pages.
> + * Ignored if @nent_p is NULL.
> *
> * Returns: A pointer to an initialized scatterlist or %NULL upon failure.
> */
> @@ -604,16 +607,15 @@ struct scatterlist *sgl_alloc_order(unsigned long long length,
> unsigned int nent, nalloc;
> u32 elem_len;
>
> - nent = round_up(length, PAGE_SIZE << order) >> (PAGE_SHIFT + order);
> - /* Check for integer overflow */
> - if (length > (nent << (PAGE_SHIFT + order)))
> + if (length >> (PAGE_SHIFT + order) >= UINT_MAX)
> return NULL;
> - nalloc = nent;
> + nent = DIV_ROUND_UP(length, PAGE_SIZE << order);
> +
This would be clearer to make nent/etc an unsigned long long. Then
check if nalloc is > SIZE_MAX before casting it to size_t for the
allocation. Avoids the wonky if statement.
Kaspm
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v7 1/4] sgl_alloc_order: remove 4 GiB limit
2022-02-01 3:49 ` [PATCH v7 1/4] sgl_alloc_order: remove 4 GiB limit Douglas Gilbert
2022-02-01 12:36 ` Jason Gunthorpe
@ 2022-02-01 13:46 ` kernel test robot
1 sibling, 0 replies; 7+ messages in thread
From: kernel test robot @ 2022-02-01 13:46 UTC (permalink / raw)
To: Douglas Gilbert, linux-scsi, linux-block
Cc: kbuild-all, martin.petersen, jejb, hare, bvanassche,
Jason Gunthorpe, Bodo Stroesser
Hi Douglas,
Thank you for the patch! Yet something to improve:
[auto build test ERROR on linus/master]
[also build test ERROR on v5.17-rc2 next-20220131]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]
url: https://github.com/0day-ci/linux/commits/Douglas-Gilbert/scatterlist-add-new-capabilities/20220201-115047
base: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git 26291c54e111ff6ba87a164d85d4a4e134b7315c
config: i386-randconfig-a003-20220131 (https://download.01.org/0day-ci/archive/20220201/202202012125.JGVcLupw-lkp@intel.com/config)
compiler: gcc-9 (Debian 9.3.0-22) 9.3.0
reproduce (this is a W=1 build):
# https://github.com/0day-ci/linux/commit/be1e80a043970c400c00709be739ab26f931331a
git remote add linux-review https://github.com/0day-ci/linux
git fetch --no-tags linux-review Douglas-Gilbert/scatterlist-add-new-capabilities/20220201-115047
git checkout be1e80a043970c400c00709be739ab26f931331a
# save the config file to linux build tree
mkdir build_dir
make W=1 O=build_dir ARCH=i386 SHELL=/bin/bash
If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>
All errors (new ones prefixed by >>):
ld: lib/scatterlist.o: in function `sgl_alloc_order':
>> lib/scatterlist.c:612: undefined reference to `__udivdi3'
vim +612 lib/scatterlist.c
586
587 /**
588 * sgl_alloc_order - allocate a scatterlist with equally sized elements each
589 * of which has 2^@order continuous pages
590 * @length: Length in bytes of the scatterlist. Must be at least one
591 * @order: Second argument for alloc_pages(). Each sgl element size will
592 * be (PAGE_SIZE*2^@order) bytes. @order must not exceed 16.
593 * @chainable: Whether or not to allocate an extra element in the scatterlist
594 * for scatterlist chaining purposes
595 * @gfp: Memory allocation flags
596 * @nent_p: [out] Number of entries in the scatterlist that have pages.
597 * Ignored if @nent_p is NULL.
598 *
599 * Returns: A pointer to an initialized scatterlist or %NULL upon failure.
600 */
601 struct scatterlist *sgl_alloc_order(unsigned long long length,
602 unsigned int order, bool chainable,
603 gfp_t gfp, unsigned int *nent_p)
604 {
605 struct scatterlist *sgl, *sg;
606 struct page *page;
607 unsigned int nent, nalloc;
608 u32 elem_len;
609
610 if (length >> (PAGE_SHIFT + order) >= UINT_MAX)
611 return NULL;
> 612 nent = DIV_ROUND_UP(length, PAGE_SIZE << order);
613
614 if (chainable) {
615 if (check_add_overflow(nent, 1U, &nalloc))
616 return NULL;
617 } else {
618 nalloc = nent;
619 }
620 sgl = kmalloc_array(nalloc, sizeof(struct scatterlist),
621 gfp & ~GFP_DMA);
622 if (!sgl)
623 return NULL;
624
625 sg_init_table(sgl, nalloc);
626 sg = sgl;
627 while (length) {
628 elem_len = min_t(u64, length, PAGE_SIZE << order);
629 page = alloc_pages(gfp, order);
630 if (!page) {
631 sgl_free_order(sgl, order);
632 return NULL;
633 }
634
635 sg_set_page(sg, page, elem_len, 0);
636 length -= elem_len;
637 sg = sg_next(sg);
638 }
639 WARN_ONCE(length, "length = %lld\n", length);
640 if (nent_p)
641 *nent_p = nent;
642 return sgl;
643 }
644 EXPORT_SYMBOL(sgl_alloc_order);
645
---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2022-02-01 13:47 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2022-02-01 3:49 [PATCH v7 0/4] scatterlist: add new capabilities Douglas Gilbert
2022-02-01 3:49 ` [PATCH v7 1/4] sgl_alloc_order: remove 4 GiB limit Douglas Gilbert
2022-02-01 12:36 ` Jason Gunthorpe
2022-02-01 13:46 ` kernel test robot
2022-02-01 3:49 ` [PATCH v7 2/4] scatterlist: add sgl_copy_sgl() function Douglas Gilbert
2022-02-01 3:49 ` [PATCH v7 3/4] scatterlist: add sgl_equal_sgl() function Douglas Gilbert
2022-02-01 3:49 ` [PATCH v7 4/4] scatterlist: add sgl_memset() Douglas Gilbert
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).