diff options
author | Toshi Kani <toshi.kani@hpe.com> | 2017-02-03 16:13:23 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2017-02-09 02:08:28 -0500 |
commit | 6cb0497aec810617388dfe674209cd417f509844 (patch) | |
tree | 5df982beb0ad419aae52f2382cba706d3cb46196 | |
parent | 72f7419610c838abc5e3fde87835a5581e8e368c (diff) |
base/memory, hotplug: fix a kernel oops in show_valid_zones()
commit a96dfddbcc04336bbed50dc2b24823e45e09e80c upstream.
Reading a sysfs "memoryN/valid_zones" file leads to the following oops
when the first page of a range is not backed by struct page.
show_valid_zones() assumes that 'start_pfn' is always valid for
page_zone().
BUG: unable to handle kernel paging request at ffffea017a000000
IP: show_valid_zones+0x6f/0x160
This issue may happen on x86-64 systems with 64GiB or more memory since
their memory block size is bumped up to 2GiB. [1] An example of such
systems is desribed below. 0x3240000000 is only aligned by 1GiB and
this memory block starts from 0x3200000000, which is not backed by
struct page.
BIOS-e820: [mem 0x0000003240000000-0x000000603fffffff] usable
Since test_pages_in_a_zone() already checks holes, fix this issue by
extending this function to return 'valid_start' and 'valid_end' for a
given range. show_valid_zones() then proceeds with the valid range.
[1] 'Commit bdee237c0343 ("x86: mm: Use 2GB memory block size on
large-memory x86-64 systems")'
Link: http://lkml.kernel.org/r/20170127222149.30893-3-toshi.kani@hpe.com
Signed-off-by: Toshi Kani <toshi.kani@hpe.com>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Zhang Zhen <zhenzhang.zhang@huawei.com>
Cc: Reza Arbab <arbab@linux.vnet.ibm.com>
Cc: David Rientjes <rientjes@google.com>
Cc: Dan Williams <dan.j.williams@intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | drivers/base/memory.c | 12 | ||||
-rw-r--r-- | include/linux/memory_hotplug.h | 3 | ||||
-rw-r--r-- | mm/memory_hotplug.c | 20 |
3 files changed, 23 insertions, 12 deletions
diff --git a/drivers/base/memory.c b/drivers/base/memory.c index e7f86a8887d2..c5cdd190b781 100644 --- a/drivers/base/memory.c +++ b/drivers/base/memory.c | |||
@@ -391,33 +391,33 @@ static ssize_t show_valid_zones(struct device *dev, | |||
391 | { | 391 | { |
392 | struct memory_block *mem = to_memory_block(dev); | 392 | struct memory_block *mem = to_memory_block(dev); |
393 | unsigned long start_pfn, end_pfn; | 393 | unsigned long start_pfn, end_pfn; |
394 | unsigned long valid_start, valid_end, valid_pages; | ||
394 | unsigned long nr_pages = PAGES_PER_SECTION * sections_per_block; | 395 | unsigned long nr_pages = PAGES_PER_SECTION * sections_per_block; |
395 | struct page *first_page; | ||
396 | struct zone *zone; | 396 | struct zone *zone; |
397 | int zone_shift = 0; | 397 | int zone_shift = 0; |
398 | 398 | ||
399 | start_pfn = section_nr_to_pfn(mem->start_section_nr); | 399 | start_pfn = section_nr_to_pfn(mem->start_section_nr); |
400 | end_pfn = start_pfn + nr_pages; | 400 | end_pfn = start_pfn + nr_pages; |
401 | first_page = pfn_to_page(start_pfn); | ||
402 | 401 | ||
403 | /* The block contains more than one zone can not be offlined. */ | 402 | /* The block contains more than one zone can not be offlined. */ |
404 | if (!test_pages_in_a_zone(start_pfn, end_pfn)) | 403 | if (!test_pages_in_a_zone(start_pfn, end_pfn, &valid_start, &valid_end)) |
405 | return sprintf(buf, "none\n"); | 404 | return sprintf(buf, "none\n"); |
406 | 405 | ||
407 | zone = page_zone(first_page); | 406 | zone = page_zone(pfn_to_page(valid_start)); |
407 | valid_pages = valid_end - valid_start; | ||
408 | 408 | ||
409 | /* MMOP_ONLINE_KEEP */ | 409 | /* MMOP_ONLINE_KEEP */ |
410 | sprintf(buf, "%s", zone->name); | 410 | sprintf(buf, "%s", zone->name); |
411 | 411 | ||
412 | /* MMOP_ONLINE_KERNEL */ | 412 | /* MMOP_ONLINE_KERNEL */ |
413 | zone_can_shift(start_pfn, nr_pages, ZONE_NORMAL, &zone_shift); | 413 | zone_can_shift(valid_start, valid_pages, ZONE_NORMAL, &zone_shift); |
414 | if (zone_shift) { | 414 | if (zone_shift) { |
415 | strcat(buf, " "); | 415 | strcat(buf, " "); |
416 | strcat(buf, (zone + zone_shift)->name); | 416 | strcat(buf, (zone + zone_shift)->name); |
417 | } | 417 | } |
418 | 418 | ||
419 | /* MMOP_ONLINE_MOVABLE */ | 419 | /* MMOP_ONLINE_MOVABLE */ |
420 | zone_can_shift(start_pfn, nr_pages, ZONE_MOVABLE, &zone_shift); | 420 | zone_can_shift(valid_start, valid_pages, ZONE_MOVABLE, &zone_shift); |
421 | if (zone_shift) { | 421 | if (zone_shift) { |
422 | strcat(buf, " "); | 422 | strcat(buf, " "); |
423 | strcat(buf, (zone + zone_shift)->name); | 423 | strcat(buf, (zone + zone_shift)->name); |
diff --git a/include/linux/memory_hotplug.h b/include/linux/memory_hotplug.h index c1784c0b4f35..134a2f69c21a 100644 --- a/include/linux/memory_hotplug.h +++ b/include/linux/memory_hotplug.h | |||
@@ -85,7 +85,8 @@ extern int zone_grow_waitqueues(struct zone *zone, unsigned long nr_pages); | |||
85 | extern int add_one_highpage(struct page *page, int pfn, int bad_ppro); | 85 | extern int add_one_highpage(struct page *page, int pfn, int bad_ppro); |
86 | /* VM interface that may be used by firmware interface */ | 86 | /* VM interface that may be used by firmware interface */ |
87 | extern int online_pages(unsigned long, unsigned long, int); | 87 | extern int online_pages(unsigned long, unsigned long, int); |
88 | extern int test_pages_in_a_zone(unsigned long, unsigned long); | 88 | extern int test_pages_in_a_zone(unsigned long start_pfn, unsigned long end_pfn, |
89 | unsigned long *valid_start, unsigned long *valid_end); | ||
89 | extern void __offline_isolated_pages(unsigned long, unsigned long); | 90 | extern void __offline_isolated_pages(unsigned long, unsigned long); |
90 | 91 | ||
91 | typedef void (*online_page_callback_t)(struct page *page); | 92 | typedef void (*online_page_callback_t)(struct page *page); |
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c index 3a330d7cae69..ede137345a99 100644 --- a/mm/memory_hotplug.c +++ b/mm/memory_hotplug.c | |||
@@ -1484,10 +1484,13 @@ bool is_mem_section_removable(unsigned long start_pfn, unsigned long nr_pages) | |||
1484 | 1484 | ||
1485 | /* | 1485 | /* |
1486 | * Confirm all pages in a range [start, end) belong to the same zone. | 1486 | * Confirm all pages in a range [start, end) belong to the same zone. |
1487 | * When true, return its valid [start, end). | ||
1487 | */ | 1488 | */ |
1488 | int test_pages_in_a_zone(unsigned long start_pfn, unsigned long end_pfn) | 1489 | int test_pages_in_a_zone(unsigned long start_pfn, unsigned long end_pfn, |
1490 | unsigned long *valid_start, unsigned long *valid_end) | ||
1489 | { | 1491 | { |
1490 | unsigned long pfn, sec_end_pfn; | 1492 | unsigned long pfn, sec_end_pfn; |
1493 | unsigned long start, end; | ||
1491 | struct zone *zone = NULL; | 1494 | struct zone *zone = NULL; |
1492 | struct page *page; | 1495 | struct page *page; |
1493 | int i; | 1496 | int i; |
@@ -1509,14 +1512,20 @@ int test_pages_in_a_zone(unsigned long start_pfn, unsigned long end_pfn) | |||
1509 | page = pfn_to_page(pfn + i); | 1512 | page = pfn_to_page(pfn + i); |
1510 | if (zone && page_zone(page) != zone) | 1513 | if (zone && page_zone(page) != zone) |
1511 | return 0; | 1514 | return 0; |
1515 | if (!zone) | ||
1516 | start = pfn + i; | ||
1512 | zone = page_zone(page); | 1517 | zone = page_zone(page); |
1518 | end = pfn + MAX_ORDER_NR_PAGES; | ||
1513 | } | 1519 | } |
1514 | } | 1520 | } |
1515 | 1521 | ||
1516 | if (zone) | 1522 | if (zone) { |
1523 | *valid_start = start; | ||
1524 | *valid_end = end; | ||
1517 | return 1; | 1525 | return 1; |
1518 | else | 1526 | } else { |
1519 | return 0; | 1527 | return 0; |
1528 | } | ||
1520 | } | 1529 | } |
1521 | 1530 | ||
1522 | /* | 1531 | /* |
@@ -1863,6 +1872,7 @@ static int __ref __offline_pages(unsigned long start_pfn, | |||
1863 | long offlined_pages; | 1872 | long offlined_pages; |
1864 | int ret, drain, retry_max, node; | 1873 | int ret, drain, retry_max, node; |
1865 | unsigned long flags; | 1874 | unsigned long flags; |
1875 | unsigned long valid_start, valid_end; | ||
1866 | struct zone *zone; | 1876 | struct zone *zone; |
1867 | struct memory_notify arg; | 1877 | struct memory_notify arg; |
1868 | 1878 | ||
@@ -1873,10 +1883,10 @@ static int __ref __offline_pages(unsigned long start_pfn, | |||
1873 | return -EINVAL; | 1883 | return -EINVAL; |
1874 | /* This makes hotplug much easier...and readable. | 1884 | /* This makes hotplug much easier...and readable. |
1875 | we assume this for now. .*/ | 1885 | we assume this for now. .*/ |
1876 | if (!test_pages_in_a_zone(start_pfn, end_pfn)) | 1886 | if (!test_pages_in_a_zone(start_pfn, end_pfn, &valid_start, &valid_end)) |
1877 | return -EINVAL; | 1887 | return -EINVAL; |
1878 | 1888 | ||
1879 | zone = page_zone(pfn_to_page(start_pfn)); | 1889 | zone = page_zone(pfn_to_page(valid_start)); |
1880 | node = zone_to_nid(zone); | 1890 | node = zone_to_nid(zone); |
1881 | nr_pages = end_pfn - start_pfn; | 1891 | nr_pages = end_pfn - start_pfn; |
1882 | 1892 | ||