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> Cc: <stable@vger.kernel.org> [4.4+]
Unified · Split
| @@ -389,33 +389,33 @@ static ssize_t show_valid_zones(struct device *dev, · 388 unmodified lines | ||
| 389 | 389 | { |
| 390 | 390 | struct memory_block *mem = to_memory_block(dev); |
| 391 | 391 | unsigned long start_pfn, end_pfn; |
| 392 | + unsigned long valid_start, valid_end, valid_pages; | |
| 392 | 393 | unsigned long nr_pages = PAGES_PER_SECTION * sections_per_block; |
| 393 | − struct page *first_page; | |
| 394 | 394 | struct zone *zone; |
| 395 | 395 | int zone_shift = 0; |
| 396 | 396 | |
| 397 | 397 | start_pfn = section_nr_to_pfn(mem->start_section_nr); |
| 398 | 398 | end_pfn = start_pfn + nr_pages; |
| 399 | − first_page = pfn_to_page(start_pfn); | |
| 400 | 399 | |
| 401 | 400 | /* The block contains more than one zone can not be offlined. */ |
| 402 | − if (!test_pages_in_a_zone(start_pfn, end_pfn)) | |
| 401 | + if (!test_pages_in_a_zone(start_pfn, end_pfn, &valid_start, &valid_end)) | |
| 403 | 402 | return sprintf(buf, "none\n"); |
| 404 | 403 | |
| 405 | − zone = page_zone(first_page); | |
| 404 | + zone = page_zone(pfn_to_page(valid_start)); | |
| 405 | + valid_pages = valid_end - valid_start; | |
| 406 | 406 | |
| 407 | 407 | /* MMOP_ONLINE_KEEP */ |
| 408 | 408 | sprintf(buf, "%s", zone->name); |
| 409 | 409 | |
| 410 | 410 | /* MMOP_ONLINE_KERNEL */ |
| 411 | − zone_can_shift(start_pfn, nr_pages, ZONE_NORMAL, &zone_shift); | |
| 411 | + zone_can_shift(valid_start, valid_pages, ZONE_NORMAL, &zone_shift); | |
| 412 | 412 | if (zone_shift) { |
| 413 | 413 | strcat(buf, " "); |
| 414 | 414 | strcat(buf, (zone + zone_shift)->name); |
| 415 | 415 | } |
| 416 | 416 | |
| 417 | 417 | /* MMOP_ONLINE_MOVABLE */ |
| 418 | − zone_can_shift(start_pfn, nr_pages, ZONE_MOVABLE, &zone_shift); | |
| 418 | + zone_can_shift(valid_start, valid_pages, ZONE_MOVABLE, &zone_shift); | |
| 419 | 419 | if (zone_shift) { |
| 420 | 420 | strcat(buf, " "); |
| 421 | 421 | strcat(buf, (zone + zone_shift)->name); |
| @@ -85,7 +85,8 @@ extern int zone_grow_waitqueues(struct zone *zone, unsigned long nr_pages); · 84 unmodified lines | ||
| 85 | 85 | extern int add_one_highpage(struct page *page, int pfn, int bad_ppro); |
| 86 | 86 | /* VM interface that may be used by firmware interface */ |
| 87 | 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 | 90 | extern void __offline_isolated_pages(unsigned long, unsigned long); |
| 90 | 91 | |
| 91 | 92 | typedef void (*online_page_callback_t)(struct page *page); |
| @@ -1484,10 +1484,13 @@ bool is_mem_section_removable(unsigned long start_pfn, unsigned long nr_pages) · 1483 unmodified lines | ||
| 1484 | 1484 | |
| 1485 | 1485 | /* |
| 1486 | 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 | 1492 | unsigned long pfn, sec_end_pfn; |
| 1493 | + unsigned long start, end; | |
| 1491 | 1494 | struct zone *zone = NULL; |
| 1492 | 1495 | struct page *page; |
| 1493 | 1496 | int i; |
| @@ -1509,14 +1512,20 @@ int test_pages_in_a_zone(unsigned long start_pfn, unsigned long end_pfn) · 15 unmodified lines | ||
| 1509 | 1512 | page = pfn_to_page(pfn + i); |
| 1510 | 1513 | if (zone && page_zone(page) != zone) |
| 1511 | 1514 | return 0; |
| 1515 | + if (!zone) | |
| 1516 | + start = pfn + i; | |
| 1512 | 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 | 1525 | return 1; |
| 1518 | − else | |
| 1526 | + } else { | |
| 1519 | 1527 | return 0; |
| 1528 | + } | |
| 1520 | 1529 | } |
| 1521 | 1530 | |
| 1522 | 1531 | /* |
| @@ -1843,6 +1852,7 @@ static int __ref __offline_pages(unsigned long start_pfn, · 320 unmodified lines | ||
| 1843 | 1852 | long offlined_pages; |
| 1844 | 1853 | int ret, drain, retry_max, node; |
| 1845 | 1854 | unsigned long flags; |
| 1855 | + unsigned long valid_start, valid_end; | |
| 1846 | 1856 | struct zone *zone; |
| 1847 | 1857 | struct memory_notify arg; |
| 1848 | 1858 | |
| @@ -1853,10 +1863,10 @@ static int __ref __offline_pages(unsigned long start_pfn, · 4 unmodified lines | ||
| 1853 | 1863 | return -EINVAL; |
| 1854 | 1864 | /* This makes hotplug much easier...and readable. |
| 1855 | 1865 | we assume this for now. .*/ |
| 1856 | − if (!test_pages_in_a_zone(start_pfn, end_pfn)) | |
| 1866 | + if (!test_pages_in_a_zone(start_pfn, end_pfn, &valid_start, &valid_end)) | |
| 1857 | 1867 | return -EINVAL; |
| 1858 | 1868 | |
| 1859 | − zone = page_zone(pfn_to_page(start_pfn)); | |
| 1869 | + zone = page_zone(pfn_to_page(valid_start)); | |
| 1860 | 1870 | node = zone_to_nid(zone); |
| 1861 | 1871 | nr_pages = end_pfn - start_pfn; |
| 1862 | 1872 | |