cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

badrange.c (7921B)


      1// SPDX-License-Identifier: GPL-2.0-only
      2/*
      3 * Copyright(c) 2017 Intel Corporation. All rights reserved.
      4 */
      5#include <linux/libnvdimm.h>
      6#include <linux/badblocks.h>
      7#include <linux/export.h>
      8#include <linux/module.h>
      9#include <linux/blkdev.h>
     10#include <linux/device.h>
     11#include <linux/ctype.h>
     12#include <linux/ndctl.h>
     13#include <linux/mutex.h>
     14#include <linux/slab.h>
     15#include <linux/io.h>
     16#include "nd-core.h"
     17#include "nd.h"
     18
     19void badrange_init(struct badrange *badrange)
     20{
     21	INIT_LIST_HEAD(&badrange->list);
     22	spin_lock_init(&badrange->lock);
     23}
     24EXPORT_SYMBOL_GPL(badrange_init);
     25
     26static void append_badrange_entry(struct badrange *badrange,
     27		struct badrange_entry *bre, u64 addr, u64 length)
     28{
     29	lockdep_assert_held(&badrange->lock);
     30	bre->start = addr;
     31	bre->length = length;
     32	list_add_tail(&bre->list, &badrange->list);
     33}
     34
     35static int alloc_and_append_badrange_entry(struct badrange *badrange,
     36		u64 addr, u64 length, gfp_t flags)
     37{
     38	struct badrange_entry *bre;
     39
     40	bre = kzalloc(sizeof(*bre), flags);
     41	if (!bre)
     42		return -ENOMEM;
     43
     44	append_badrange_entry(badrange, bre, addr, length);
     45	return 0;
     46}
     47
     48static int add_badrange(struct badrange *badrange, u64 addr, u64 length)
     49{
     50	struct badrange_entry *bre, *bre_new;
     51
     52	spin_unlock(&badrange->lock);
     53	bre_new = kzalloc(sizeof(*bre_new), GFP_KERNEL);
     54	spin_lock(&badrange->lock);
     55
     56	if (list_empty(&badrange->list)) {
     57		if (!bre_new)
     58			return -ENOMEM;
     59		append_badrange_entry(badrange, bre_new, addr, length);
     60		return 0;
     61	}
     62
     63	/*
     64	 * There is a chance this is a duplicate, check for those first.
     65	 * This will be the common case as ARS_STATUS returns all known
     66	 * errors in the SPA space, and we can't query it per region
     67	 */
     68	list_for_each_entry(bre, &badrange->list, list)
     69		if (bre->start == addr) {
     70			/* If length has changed, update this list entry */
     71			if (bre->length != length)
     72				bre->length = length;
     73			kfree(bre_new);
     74			return 0;
     75		}
     76
     77	/*
     78	 * If not a duplicate or a simple length update, add the entry as is,
     79	 * as any overlapping ranges will get resolved when the list is consumed
     80	 * and converted to badblocks
     81	 */
     82	if (!bre_new)
     83		return -ENOMEM;
     84	append_badrange_entry(badrange, bre_new, addr, length);
     85
     86	return 0;
     87}
     88
     89int badrange_add(struct badrange *badrange, u64 addr, u64 length)
     90{
     91	int rc;
     92
     93	spin_lock(&badrange->lock);
     94	rc = add_badrange(badrange, addr, length);
     95	spin_unlock(&badrange->lock);
     96
     97	return rc;
     98}
     99EXPORT_SYMBOL_GPL(badrange_add);
    100
    101void badrange_forget(struct badrange *badrange, phys_addr_t start,
    102		unsigned int len)
    103{
    104	struct list_head *badrange_list = &badrange->list;
    105	u64 clr_end = start + len - 1;
    106	struct badrange_entry *bre, *next;
    107
    108	spin_lock(&badrange->lock);
    109
    110	/*
    111	 * [start, clr_end] is the badrange interval being cleared.
    112	 * [bre->start, bre_end] is the badrange_list entry we're comparing
    113	 * the above interval against. The badrange list entry may need
    114	 * to be modified (update either start or length), deleted, or
    115	 * split into two based on the overlap characteristics
    116	 */
    117
    118	list_for_each_entry_safe(bre, next, badrange_list, list) {
    119		u64 bre_end = bre->start + bre->length - 1;
    120
    121		/* Skip intervals with no intersection */
    122		if (bre_end < start)
    123			continue;
    124		if (bre->start >  clr_end)
    125			continue;
    126		/* Delete completely overlapped badrange entries */
    127		if ((bre->start >= start) && (bre_end <= clr_end)) {
    128			list_del(&bre->list);
    129			kfree(bre);
    130			continue;
    131		}
    132		/* Adjust start point of partially cleared entries */
    133		if ((start <= bre->start) && (clr_end > bre->start)) {
    134			bre->length -= clr_end - bre->start + 1;
    135			bre->start = clr_end + 1;
    136			continue;
    137		}
    138		/* Adjust bre->length for partial clearing at the tail end */
    139		if ((bre->start < start) && (bre_end <= clr_end)) {
    140			/* bre->start remains the same */
    141			bre->length = start - bre->start;
    142			continue;
    143		}
    144		/*
    145		 * If clearing in the middle of an entry, we split it into
    146		 * two by modifying the current entry to represent one half of
    147		 * the split, and adding a new entry for the second half.
    148		 */
    149		if ((bre->start < start) && (bre_end > clr_end)) {
    150			u64 new_start = clr_end + 1;
    151			u64 new_len = bre_end - new_start + 1;
    152
    153			/* Add new entry covering the right half */
    154			alloc_and_append_badrange_entry(badrange, new_start,
    155					new_len, GFP_NOWAIT);
    156			/* Adjust this entry to cover the left half */
    157			bre->length = start - bre->start;
    158			continue;
    159		}
    160	}
    161	spin_unlock(&badrange->lock);
    162}
    163EXPORT_SYMBOL_GPL(badrange_forget);
    164
    165static void set_badblock(struct badblocks *bb, sector_t s, int num)
    166{
    167	dev_dbg(bb->dev, "Found a bad range (0x%llx, 0x%llx)\n",
    168			(u64) s * 512, (u64) num * 512);
    169	/* this isn't an error as the hardware will still throw an exception */
    170	if (badblocks_set(bb, s, num, 1))
    171		dev_info_once(bb->dev, "%s: failed for sector %llx\n",
    172				__func__, (u64) s);
    173}
    174
    175/**
    176 * __add_badblock_range() - Convert a physical address range to bad sectors
    177 * @bb:		badblocks instance to populate
    178 * @ns_offset:	namespace offset where the error range begins (in bytes)
    179 * @len:	number of bytes of badrange to be added
    180 *
    181 * This assumes that the range provided with (ns_offset, len) is within
    182 * the bounds of physical addresses for this namespace, i.e. lies in the
    183 * interval [ns_start, ns_start + ns_size)
    184 */
    185static void __add_badblock_range(struct badblocks *bb, u64 ns_offset, u64 len)
    186{
    187	const unsigned int sector_size = 512;
    188	sector_t start_sector, end_sector;
    189	u64 num_sectors;
    190	u32 rem;
    191
    192	start_sector = div_u64(ns_offset, sector_size);
    193	end_sector = div_u64_rem(ns_offset + len, sector_size, &rem);
    194	if (rem)
    195		end_sector++;
    196	num_sectors = end_sector - start_sector;
    197
    198	if (unlikely(num_sectors > (u64)INT_MAX)) {
    199		u64 remaining = num_sectors;
    200		sector_t s = start_sector;
    201
    202		while (remaining) {
    203			int done = min_t(u64, remaining, INT_MAX);
    204
    205			set_badblock(bb, s, done);
    206			remaining -= done;
    207			s += done;
    208		}
    209	} else
    210		set_badblock(bb, start_sector, num_sectors);
    211}
    212
    213static void badblocks_populate(struct badrange *badrange,
    214		struct badblocks *bb, const struct range *range)
    215{
    216	struct badrange_entry *bre;
    217
    218	if (list_empty(&badrange->list))
    219		return;
    220
    221	list_for_each_entry(bre, &badrange->list, list) {
    222		u64 bre_end = bre->start + bre->length - 1;
    223
    224		/* Discard intervals with no intersection */
    225		if (bre_end < range->start)
    226			continue;
    227		if (bre->start > range->end)
    228			continue;
    229		/* Deal with any overlap after start of the namespace */
    230		if (bre->start >= range->start) {
    231			u64 start = bre->start;
    232			u64 len;
    233
    234			if (bre_end <= range->end)
    235				len = bre->length;
    236			else
    237				len = range->start + range_len(range)
    238					- bre->start;
    239			__add_badblock_range(bb, start - range->start, len);
    240			continue;
    241		}
    242		/*
    243		 * Deal with overlap for badrange starting before
    244		 * the namespace.
    245		 */
    246		if (bre->start < range->start) {
    247			u64 len;
    248
    249			if (bre_end < range->end)
    250				len = bre->start + bre->length - range->start;
    251			else
    252				len = range_len(range);
    253			__add_badblock_range(bb, 0, len);
    254		}
    255	}
    256}
    257
    258/**
    259 * nvdimm_badblocks_populate() - Convert a list of badranges to badblocks
    260 * @region: parent region of the range to interrogate
    261 * @bb: badblocks instance to populate
    262 * @res: resource range to consider
    263 *
    264 * The badrange list generated during bus initialization may contain
    265 * multiple, possibly overlapping physical address ranges.  Compare each
    266 * of these ranges to the resource range currently being initialized,
    267 * and add badblocks entries for all matching sub-ranges
    268 */
    269void nvdimm_badblocks_populate(struct nd_region *nd_region,
    270		struct badblocks *bb, const struct range *range)
    271{
    272	struct nvdimm_bus *nvdimm_bus;
    273
    274	if (!is_memory(&nd_region->dev)) {
    275		dev_WARN_ONCE(&nd_region->dev, 1,
    276				"%s only valid for pmem regions\n", __func__);
    277		return;
    278	}
    279	nvdimm_bus = walk_to_nvdimm_bus(&nd_region->dev);
    280
    281	nvdimm_bus_lock(&nvdimm_bus->dev);
    282	badblocks_populate(&nvdimm_bus->badrange, bb, range);
    283	nvdimm_bus_unlock(&nvdimm_bus->dev);
    284}
    285EXPORT_SYMBOL_GPL(nvdimm_badblocks_populate);