aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames Bottomley <JBottomley@Odin.com>2015-03-05 21:47:01 -0500
committerJames Bottomley <JBottomley@Odin.com>2015-04-10 19:27:48 -0400
commitb9f28d863594c429e1df35a0474d2663ca28b307 (patch)
tree75a15cd66637d33e6d8e6e0c6cdd67e44790b376
parentcad2e03d8607793fd71a830b4b41fc8e9e9995ea (diff)
sd, mmc, virtio_blk, string_helpers: fix block size units
The current string_get_size() overflows when the device size goes over 2^64 bytes because the string helper routine computes the suffix from the size in bytes. However, the entirety of Linux thinks in terms of blocks, not bytes, so this will artificially induce an overflow on very large devices. Fix this by making the function string_get_size() take blocks and the block size instead of bytes. This should allow us to keep working until the current SCSI standard overflows. Also fix virtio_blk and mmc (both of which were also artificially multiplying by the block size to pass a byte side to string_get_size()). The mathematics of this is pretty simple: we're taking a product of size in blocks (S) and block size (B) and trying to re-express this in exponential form: S*B = R*N^E (where N, the exponent is either 1000 or 1024) and R < N. Mathematically, S = RS*N^ES and B=RB*N^EB, so if RS*RB < N it's easy to see that S*B = RS*RB*N^(ES+EB). However, if RS*BS > N, we can see that this can be re-expressed as RS*BS = R*N (where R = RS*BS/N < N) so the whole exponent becomes R*N^(ES+EB+1) [jejb: fix incorrect 32 bit do_div spotted by kbuild test robot <fengguang.wu@intel.com>] Acked-by: Ulf Hansson <ulf.hansson@linaro.org> Reviewed-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: James Bottomley <JBottomley@Odin.com>
-rw-r--r--drivers/block/virtio_blk.c9
-rw-r--r--drivers/mmc/card/block.c4
-rw-r--r--drivers/scsi/sd.c8
-rw-r--r--include/linux/string_helpers.h2
-rw-r--r--lib/string_helpers.c68
5 files changed, 60 insertions, 31 deletions
diff --git a/drivers/block/virtio_blk.c b/drivers/block/virtio_blk.c
index 655e570b9b31..5ea2f0bbbc7c 100644
--- a/drivers/block/virtio_blk.c
+++ b/drivers/block/virtio_blk.c
@@ -342,7 +342,7 @@ static void virtblk_config_changed_work(struct work_struct *work)
342 struct request_queue *q = vblk->disk->queue; 342 struct request_queue *q = vblk->disk->queue;
343 char cap_str_2[10], cap_str_10[10]; 343 char cap_str_2[10], cap_str_10[10];
344 char *envp[] = { "RESIZE=1", NULL }; 344 char *envp[] = { "RESIZE=1", NULL };
345 u64 capacity, size; 345 u64 capacity;
346 346
347 /* Host must always specify the capacity. */ 347 /* Host must always specify the capacity. */
348 virtio_cread(vdev, struct virtio_blk_config, capacity, &capacity); 348 virtio_cread(vdev, struct virtio_blk_config, capacity, &capacity);
@@ -354,9 +354,10 @@ static void virtblk_config_changed_work(struct work_struct *work)
354 capacity = (sector_t)-1; 354 capacity = (sector_t)-1;
355 } 355 }
356 356
357 size = capacity * queue_logical_block_size(q); 357 string_get_size(capacity, queue_logical_block_size(q),
358 string_get_size(size, STRING_UNITS_2, cap_str_2, sizeof(cap_str_2)); 358 STRING_UNITS_2, cap_str_2, sizeof(cap_str_2));
359 string_get_size(size, STRING_UNITS_10, cap_str_10, sizeof(cap_str_10)); 359 string_get_size(capacity, queue_logical_block_size(q),
360 STRING_UNITS_10, cap_str_10, sizeof(cap_str_10));
360 361
361 dev_notice(&vdev->dev, 362 dev_notice(&vdev->dev,
362 "new size: %llu %d-byte logical blocks (%s/%s)\n", 363 "new size: %llu %d-byte logical blocks (%s/%s)\n",
diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c
index c69afb5e264e..2fc426926574 100644
--- a/drivers/mmc/card/block.c
+++ b/drivers/mmc/card/block.c
@@ -2230,7 +2230,7 @@ static int mmc_blk_alloc_part(struct mmc_card *card,
2230 part_md->part_type = part_type; 2230 part_md->part_type = part_type;
2231 list_add(&part_md->part, &md->part); 2231 list_add(&part_md->part, &md->part);
2232 2232
2233 string_get_size((u64)get_capacity(part_md->disk) << 9, STRING_UNITS_2, 2233 string_get_size((u64)get_capacity(part_md->disk), 512, STRING_UNITS_2,
2234 cap_str, sizeof(cap_str)); 2234 cap_str, sizeof(cap_str));
2235 pr_info("%s: %s %s partition %u %s\n", 2235 pr_info("%s: %s %s partition %u %s\n",
2236 part_md->disk->disk_name, mmc_card_id(card), 2236 part_md->disk->disk_name, mmc_card_id(card),
@@ -2436,7 +2436,7 @@ static int mmc_blk_probe(struct device *dev)
2436 if (IS_ERR(md)) 2436 if (IS_ERR(md))
2437 return PTR_ERR(md); 2437 return PTR_ERR(md);
2438 2438
2439 string_get_size((u64)get_capacity(md->disk) << 9, STRING_UNITS_2, 2439 string_get_size((u64)get_capacity(md->disk), 512, STRING_UNITS_2,
2440 cap_str, sizeof(cap_str)); 2440 cap_str, sizeof(cap_str));
2441 pr_info("%s: %s %s %s %s\n", 2441 pr_info("%s: %s %s %s %s\n",
2442 md->disk->disk_name, mmc_card_id(card), mmc_card_name(card), 2442 md->disk->disk_name, mmc_card_id(card), mmc_card_name(card),
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
index 9e0c63e57aff..dcc42446f58a 100644
--- a/drivers/scsi/sd.c
+++ b/drivers/scsi/sd.c
@@ -2211,11 +2211,11 @@ got_data:
2211 2211
2212 { 2212 {
2213 char cap_str_2[10], cap_str_10[10]; 2213 char cap_str_2[10], cap_str_10[10];
2214 u64 sz = (u64)sdkp->capacity << ilog2(sector_size);
2215 2214
2216 string_get_size(sz, STRING_UNITS_2, cap_str_2, 2215 string_get_size(sdkp->capacity, sector_size,
2217 sizeof(cap_str_2)); 2216 STRING_UNITS_2, cap_str_2, sizeof(cap_str_2));
2218 string_get_size(sz, STRING_UNITS_10, cap_str_10, 2217 string_get_size(sdkp->capacity, sector_size,
2218 STRING_UNITS_10, cap_str_10,
2219 sizeof(cap_str_10)); 2219 sizeof(cap_str_10));
2220 2220
2221 if (sdkp->first_scan || old_capacity != sdkp->capacity) { 2221 if (sdkp->first_scan || old_capacity != sdkp->capacity) {
diff --git a/include/linux/string_helpers.h b/include/linux/string_helpers.h
index 657571817260..263328063730 100644
--- a/include/linux/string_helpers.h
+++ b/include/linux/string_helpers.h
@@ -10,7 +10,7 @@ enum string_size_units {
10 STRING_UNITS_2, /* use binary powers of 2^10 */ 10 STRING_UNITS_2, /* use binary powers of 2^10 */
11}; 11};
12 12
13void string_get_size(u64 size, enum string_size_units units, 13void string_get_size(u64 size, u64 blk_size, enum string_size_units units,
14 char *buf, int len); 14 char *buf, int len);
15 15
16#define UNESCAPE_SPACE 0x01 16#define UNESCAPE_SPACE 0x01
diff --git a/lib/string_helpers.c b/lib/string_helpers.c
index 8f8c4417f228..4a913ec3acf9 100644
--- a/lib/string_helpers.c
+++ b/lib/string_helpers.c
@@ -4,6 +4,7 @@
4 * Copyright 31 August 2008 James Bottomley 4 * Copyright 31 August 2008 James Bottomley
5 * Copyright (C) 2013, Intel Corporation 5 * Copyright (C) 2013, Intel Corporation
6 */ 6 */
7#include <linux/bug.h>
7#include <linux/kernel.h> 8#include <linux/kernel.h>
8#include <linux/math64.h> 9#include <linux/math64.h>
9#include <linux/export.h> 10#include <linux/export.h>
@@ -14,7 +15,8 @@
14 15
15/** 16/**
16 * string_get_size - get the size in the specified units 17 * string_get_size - get the size in the specified units
17 * @size: The size to be converted 18 * @size: The size to be converted in blocks
19 * @blk_size: Size of the block (use 1 for size in bytes)
18 * @units: units to use (powers of 1000 or 1024) 20 * @units: units to use (powers of 1000 or 1024)
19 * @buf: buffer to format to 21 * @buf: buffer to format to
20 * @len: length of buffer 22 * @len: length of buffer
@@ -24,14 +26,14 @@
24 * at least 9 bytes and will always be zero terminated. 26 * at least 9 bytes and will always be zero terminated.
25 * 27 *
26 */ 28 */
27void string_get_size(u64 size, const enum string_size_units units, 29void string_get_size(u64 size, u64 blk_size, const enum string_size_units units,
28 char *buf, int len) 30 char *buf, int len)
29{ 31{
30 static const char *const units_10[] = { 32 static const char *const units_10[] = {
31 "B", "kB", "MB", "GB", "TB", "PB", "EB" 33 "B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"
32 }; 34 };
33 static const char *const units_2[] = { 35 static const char *const units_2[] = {
34 "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB" 36 "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"
35 }; 37 };
36 static const char *const *const units_str[] = { 38 static const char *const *const units_str[] = {
37 [STRING_UNITS_10] = units_10, 39 [STRING_UNITS_10] = units_10,
@@ -42,31 +44,57 @@ void string_get_size(u64 size, const enum string_size_units units,
42 [STRING_UNITS_2] = 1024, 44 [STRING_UNITS_2] = 1024,
43 }; 45 };
44 int i, j; 46 int i, j;
45 u32 remainder = 0, sf_cap; 47 u32 remainder = 0, sf_cap, exp;
46 char tmp[8]; 48 char tmp[8];
49 const char *unit;
47 50
48 tmp[0] = '\0'; 51 tmp[0] = '\0';
49 i = 0; 52 i = 0;
50 if (size >= divisor[units]) { 53 if (!size)
51 while (size >= divisor[units]) { 54 goto out;
52 remainder = do_div(size, divisor[units]);
53 i++;
54 }
55 55
56 sf_cap = size; 56 while (blk_size >= divisor[units]) {
57 for (j = 0; sf_cap*10 < 1000; j++) 57 remainder = do_div(blk_size, divisor[units]);
58 sf_cap *= 10; 58 i++;
59 }
59 60
60 if (j) { 61 exp = divisor[units] / (u32)blk_size;
61 remainder *= 1000; 62 if (size >= exp) {
62 remainder /= divisor[units]; 63 remainder = do_div(size, divisor[units]);
63 snprintf(tmp, sizeof(tmp), ".%03u", remainder); 64 remainder *= blk_size;
64 tmp[j+1] = '\0'; 65 i++;
65 } 66 } else {
67 remainder *= size;
68 }
69
70 size *= blk_size;
71 size += remainder / divisor[units];
72 remainder %= divisor[units];
73
74 while (size >= divisor[units]) {
75 remainder = do_div(size, divisor[units]);
76 i++;
66 } 77 }
67 78
79 sf_cap = size;
80 for (j = 0; sf_cap*10 < 1000; j++)
81 sf_cap *= 10;
82
83 if (j) {
84 remainder *= 1000;
85 remainder /= divisor[units];
86 snprintf(tmp, sizeof(tmp), ".%03u", remainder);
87 tmp[j+1] = '\0';
88 }
89
90 out:
91 if (i >= ARRAY_SIZE(units_2))
92 unit = "UNK";
93 else
94 unit = units_str[units][i];
95
68 snprintf(buf, len, "%u%s %s", (u32)size, 96 snprintf(buf, len, "%u%s %s", (u32)size,
69 tmp, units_str[units][i]); 97 tmp, unit);
70} 98}
71EXPORT_SYMBOL(string_get_size); 99EXPORT_SYMBOL(string_get_size);
72 100