mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
Merge patch series "kexec: keep the next kernel off hardware-poisoned pages"
Breno Leitao <leitao@debian.org> says: Memory failures are common enough on large fleets that kexec regularly lands the next kernel on a frame the memory failure subsystem has already marked bad. Patch 2 teaches the segment placement to avoid those frames. Patch 1 is a prerequisite. locate_mem_hole_top_down() walks candidates downwards without ever checking that the subtraction stays above zero, so the walk can wrap and report success with a destination outside of RAM. Patch 2 adds one more downward step to that loop, so the bug is fixed first and both patches rely on the same bail-out.
This commit is contained in:
commit
c7add4fee3
|
|
@ -5254,6 +5254,8 @@ extern const struct attribute_group memory_failure_attr_group;
|
|||
extern void memory_failure_queue(unsigned long pfn, int flags);
|
||||
void num_poisoned_pages_inc(unsigned long pfn);
|
||||
void num_poisoned_pages_sub(unsigned long pfn, long i);
|
||||
phys_addr_t range_first_hwpoison(phys_addr_t start, unsigned long size);
|
||||
phys_addr_t range_last_hwpoison(phys_addr_t start, unsigned long size);
|
||||
#else
|
||||
static inline void memory_failure_queue(unsigned long pfn, int flags)
|
||||
{
|
||||
|
|
@ -5266,6 +5268,18 @@ static inline void num_poisoned_pages_inc(unsigned long pfn)
|
|||
static inline void num_poisoned_pages_sub(unsigned long pfn, long i)
|
||||
{
|
||||
}
|
||||
|
||||
static inline phys_addr_t range_first_hwpoison(phys_addr_t start,
|
||||
unsigned long size)
|
||||
{
|
||||
return PHYS_ADDR_MAX;
|
||||
}
|
||||
|
||||
static inline phys_addr_t range_last_hwpoison(phys_addr_t start,
|
||||
unsigned long size)
|
||||
{
|
||||
return PHYS_ADDR_MAX;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_MEMORY_FAILURE) && defined(CONFIG_MEMORY_HOTPLUG)
|
||||
|
|
|
|||
|
|
@ -212,6 +212,16 @@ int sanity_check_segment_list(struct kimage *image)
|
|||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Reject destinations that land on hardware-poisoned memory: the
|
||||
* relocation copy would machine-check on the bad frame.
|
||||
*/
|
||||
for (i = 0; i < nr_segments; i++) {
|
||||
if (range_first_hwpoison(image->segment[i].mem,
|
||||
image->segment[i].memsz) != PHYS_ADDR_MAX)
|
||||
return -EHWPOISON;
|
||||
}
|
||||
|
||||
/*
|
||||
* The destination addresses are searched from system RAM rather than
|
||||
* being allocated from the buddy allocator, so they are not guaranteed
|
||||
|
|
|
|||
|
|
@ -475,6 +475,7 @@ static int locate_mem_hole_top_down(unsigned long start, unsigned long end,
|
|||
{
|
||||
struct kimage *image = kbuf->image;
|
||||
unsigned long temp_start, temp_end;
|
||||
phys_addr_t poison;
|
||||
|
||||
temp_end = min(end, kbuf->buf_max);
|
||||
temp_start = temp_end - kbuf->memsz + 1;
|
||||
|
|
@ -484,7 +485,9 @@ static int locate_mem_hole_top_down(unsigned long start, unsigned long end,
|
|||
/* align down start */
|
||||
temp_start = ALIGN_DOWN(temp_start, kbuf->buf_align);
|
||||
|
||||
if (temp_start < start || temp_start < kbuf->buf_min)
|
||||
/* A candidate above the range means the walk wrapped around */
|
||||
if (temp_start < start || temp_start < kbuf->buf_min ||
|
||||
temp_start > end)
|
||||
return 0;
|
||||
|
||||
temp_end = temp_start + kbuf->memsz - 1;
|
||||
|
|
@ -504,6 +507,13 @@ static int locate_mem_hole_top_down(unsigned long start, unsigned long end,
|
|||
continue;
|
||||
}
|
||||
|
||||
poison = range_first_hwpoison(temp_start, kbuf->memsz);
|
||||
if (poison != PHYS_ADDR_MAX) {
|
||||
/* we hit a poisoned page */
|
||||
temp_start = poison - kbuf->memsz;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* We found a suitable memory range */
|
||||
break;
|
||||
} while (1);
|
||||
|
|
@ -520,6 +530,7 @@ static int locate_mem_hole_bottom_up(unsigned long start, unsigned long end,
|
|||
{
|
||||
struct kimage *image = kbuf->image;
|
||||
unsigned long temp_start, temp_end;
|
||||
phys_addr_t poison;
|
||||
|
||||
temp_start = max(start, kbuf->buf_min);
|
||||
|
||||
|
|
@ -546,6 +557,13 @@ static int locate_mem_hole_bottom_up(unsigned long start, unsigned long end,
|
|||
continue;
|
||||
}
|
||||
|
||||
poison = range_last_hwpoison(temp_start, kbuf->memsz);
|
||||
if (poison != PHYS_ADDR_MAX) {
|
||||
/* we hit a poisoned page */
|
||||
temp_start = poison + PAGE_SIZE;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* We found a suitable memory range */
|
||||
break;
|
||||
} while (1);
|
||||
|
|
|
|||
|
|
@ -96,6 +96,46 @@ void num_poisoned_pages_sub(unsigned long pfn, long i)
|
|||
memblk_nr_poison_sub(pfn, i);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the first or the last hardware-poisoned online page in [start,
|
||||
* start + size), or PHYS_ADDR_MAX if the range is clean.
|
||||
*/
|
||||
static phys_addr_t range_hwpoison(phys_addr_t start, unsigned long size,
|
||||
bool first)
|
||||
{
|
||||
phys_addr_t poison = PHYS_ADDR_MAX;
|
||||
unsigned long pfn, end_pfn;
|
||||
|
||||
if (!size || !atomic_long_read(&num_poisoned_pages))
|
||||
return poison;
|
||||
|
||||
end_pfn = PHYS_PFN(start + size - 1);
|
||||
for (pfn = PHYS_PFN(start); pfn <= end_pfn; pfn++) {
|
||||
struct page *page = pfn_to_online_page(pfn);
|
||||
|
||||
if (page && is_page_hwpoison(page)) {
|
||||
if (first)
|
||||
return PFN_PHYS(pfn);
|
||||
|
||||
poison = PFN_PHYS(pfn);
|
||||
}
|
||||
|
||||
cond_resched();
|
||||
}
|
||||
|
||||
return poison;
|
||||
}
|
||||
|
||||
phys_addr_t range_first_hwpoison(phys_addr_t start, unsigned long size)
|
||||
{
|
||||
return range_hwpoison(start, size, true);
|
||||
}
|
||||
|
||||
phys_addr_t range_last_hwpoison(phys_addr_t start, unsigned long size)
|
||||
{
|
||||
return range_hwpoison(start, size, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* MF_ATTR_RO - Create sysfs entry for each memory failure statistics.
|
||||
* @_name: name of the file in the per NUMA sysfs directory.
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user