| 1 | |
| 2 | * edac_mc kernel module
|
| 3 | * (C) 2005, 2006 Linux Networx (http://lnxi.com)
|
| 4 | * This file may be distributed under the terms of the
|
| 5 | * GNU General Public License.
|
| 6 | *
|
| 7 | * Written by Thayne Harbaugh
|
| 8 | * Based on work by Dan Hollis <goemon at anime dot net> and others.
|
| 9 | * http://www.anime.net/~goemon/linux-ecc/
|
| 10 | *
|
| 11 | * Modified by Dave Peterson and Doug Thompson
|
| 12 | *
|
| 13 |
|
| 14 |
|
| 15 | #include <linux/module.h>
|
| 16 | #include <linux/proc_fs.h>
|
| 17 | #include <linux/kernel.h>
|
| 18 | #include <linux/types.h>
|
| 19 | #include <linux/smp.h>
|
| 20 | #include <linux/init.h>
|
| 21 | #include <linux/sysctl.h>
|
| 22 | #include <linux/highmem.h>
|
| 23 | #include <linux/timer.h>
|
| 24 | #include <linux/slab.h>
|
| 25 | #include <linux/jiffies.h>
|
| 26 | #include <linux/spinlock.h>
|
| 27 | #include <linux/list.h>
|
| 28 | #include <linux/ctype.h>
|
| 29 | #include <linux/edac.h>
|
| 30 | #include <linux/bitops.h>
|
| 31 | #include <linux/uaccess.h>
|
| 32 | #include <asm/page.h>
|
| 33 | #include "edac_mc.h"
|
| 34 | #include "edac_module.h"
|
| 35 | #include <ras/ras_event.h>
|
| 36 |
|
| 37 | #ifdef CONFIG_EDAC_ATOMIC_SCRUB
|
| 38 | #include <asm/edac.h>
|
| 39 | #else
|
| 40 | #define edac_atomic_scrub(va, size) do { } while (0)
|
| 41 | #endif
|
| 42 |
|
| 43 | int edac_op_state = EDAC_OPSTATE_INVAL;
|
| 44 | EXPORT_SYMBOL_GPL(edac_op_state);
|
| 45 |
|
| 46 |
|
| 47 | static DEFINE_MUTEX(mem_ctls_mutex);
|
| 48 | static LIST_HEAD(mc_devices);
|
| 49 |
|
| 50 | |
| 51 | * Used to lock EDAC MC to just one module, avoiding two drivers e. g.
|
| 52 | * apei/ghes and i7core_edac to be used at the same time.
|
| 53 |
|
| 54 | static const char *edac_mc_owner;
|
| 55 |
|
| 56 | static struct mem_ctl_info *error_desc_to_mci(struct edac_raw_error_desc *e)
|
| 57 | {
|
| 58 | return container_of(e, struct mem_ctl_info, error_desc);
|
| 59 | }
|
| 60 |
|
| 61 | unsigned int edac_dimm_info_location(struct dimm_info *dimm, char *buf,
|
| 62 | unsigned int len)
|
| 63 | {
|
| 64 | struct mem_ctl_info *mci = dimm->mci;
|
| 65 | int i, n, count = 0;
|
| 66 | char *p = buf;
|
| 67 |
|
| 68 | for (i = 0; i < mci->n_layers; i++) {
|
| 69 | n = scnprintf(p, len, "%s %d ",
|
| 70 | edac_layer_name[mci->layers[i].type],
|
| 71 | dimm->location[i]);
|
| 72 | p += n;
|
| 73 | len -= n;
|
| 74 | count += n;
|
| 75 | }
|
| 76 |
|
| 77 | return count;
|
| 78 | }
|
| 79 |
|
| 80 | #ifdef CONFIG_EDAC_DEBUG
|
| 81 |
|
| 82 | static void edac_mc_dump_channel(struct rank_info *chan)
|
| 83 | {
|
| 84 | edac_dbg(4, " channel->chan_idx = %d\n", chan->chan_idx);
|
| 85 | edac_dbg(4, " channel = %p\n", chan);
|
| 86 | edac_dbg(4, " channel->csrow = %p\n", chan->csrow);
|
| 87 | edac_dbg(4, " channel->dimm = %p\n", chan->dimm);
|
| 88 | }
|
| 89 |
|
| 90 | static void edac_mc_dump_dimm(struct dimm_info *dimm)
|
| 91 | {
|
| 92 | char location[80];
|
| 93 |
|
| 94 | if (!dimm->nr_pages)
|
| 95 | return;
|
| 96 |
|
| 97 | edac_dimm_info_location(dimm, location, sizeof(location));
|
| 98 |
|
| 99 | edac_dbg(4, "%s%i: %smapped as virtual row %d, chan %d\n",
|
| 100 | dimm->mci->csbased ? "rank" : "dimm",
|
| 101 | dimm->idx, location, dimm->csrow, dimm->cschannel);
|
| 102 | edac_dbg(4, " dimm = %p\n", dimm);
|
| 103 | edac_dbg(4, " dimm->label = '%s'\n", dimm->label);
|
| 104 | edac_dbg(4, " dimm->nr_pages = 0x%x\n", dimm->nr_pages);
|
| 105 | edac_dbg(4, " dimm->grain = %d\n", dimm->grain);
|
| 106 | }
|
| 107 |
|
| 108 | static void edac_mc_dump_csrow(struct csrow_info *csrow)
|
| 109 | {
|
| 110 | edac_dbg(4, "csrow->csrow_idx = %d\n", csrow->csrow_idx);
|
| 111 | edac_dbg(4, " csrow = %p\n", csrow);
|
| 112 | edac_dbg(4, " csrow->first_page = 0x%lx\n", csrow->first_page);
|
| 113 | edac_dbg(4, " csrow->last_page = 0x%lx\n", csrow->last_page);
|
| 114 | edac_dbg(4, " csrow->page_mask = 0x%lx\n", csrow->page_mask);
|
| 115 | edac_dbg(4, " csrow->nr_channels = %d\n", csrow->nr_channels);
|
| 116 | edac_dbg(4, " csrow->channels = %p\n", csrow->channels);
|
| 117 | edac_dbg(4, " csrow->mci = %p\n", csrow->mci);
|
| 118 | }
|
| 119 |
|
| 120 | static void edac_mc_dump_mci(struct mem_ctl_info *mci)
|
| 121 | {
|
| 122 | edac_dbg(3, "\tmci = %p\n", mci);
|
| 123 | edac_dbg(3, "\tmci->mtype_cap = %lx\n", mci->mtype_cap);
|
| 124 | edac_dbg(3, "\tmci->edac_ctl_cap = %lx\n", mci->edac_ctl_cap);
|
| 125 | edac_dbg(3, "\tmci->edac_cap = %lx\n", mci->edac_cap);
|
| 126 | edac_dbg(4, "\tmci->edac_check = %p\n", mci->edac_check);
|
| 127 | edac_dbg(3, "\tmci->nr_csrows = %d, csrows = %p\n",
|
| 128 | mci->nr_csrows, mci->csrows);
|
| 129 | edac_dbg(3, "\tmci->nr_dimms = %d, dimms = %p\n",
|
| 130 | mci->tot_dimms, mci->dimms);
|
| 131 | edac_dbg(3, "\tdev = %p\n", mci->pdev);
|
| 132 | edac_dbg(3, "\tmod_name:ctl_name = %s:%s\n",
|
| 133 | mci->mod_name, mci->ctl_name);
|
| 134 | edac_dbg(3, "\tpvt_info = %p\n\n", mci->pvt_info);
|
| 135 | }
|
| 136 |
|
| 137 | #endif
|
| 138 |
|
| 139 | const char * const edac_mem_types[] = {
|
| 140 | [MEM_EMPTY] = "Empty",
|
| 141 | [MEM_RESERVED] = "Reserved",
|
| 142 | [MEM_UNKNOWN] = "Unknown",
|
| 143 | [MEM_FPM] = "FPM",
|
| 144 | [MEM_EDO] = "EDO",
|
| 145 | [MEM_BEDO] = "BEDO",
|
| 146 | [MEM_SDR] = "Unbuffered-SDR",
|
| 147 | [MEM_RDR] = "Registered-SDR",
|
| 148 | [MEM_DDR] = "Unbuffered-DDR",
|
| 149 | [MEM_RDDR] = "Registered-DDR",
|
| 150 | [MEM_RMBS] = "RMBS",
|
| 151 | [MEM_DDR2] = "Unbuffered-DDR2",
|
| 152 | [MEM_FB_DDR2] = "FullyBuffered-DDR2",
|
| 153 | [MEM_RDDR2] = "Registered-DDR2",
|
| 154 | [MEM_XDR] = "XDR",
|
| 155 | [MEM_DDR3] = "Unbuffered-DDR3",
|
| 156 | [MEM_RDDR3] = "Registered-DDR3",
|
| 157 | [MEM_LRDDR3] = "Load-Reduced-DDR3-RAM",
|
| 158 | [MEM_LPDDR3] = "Low-Power-DDR3-RAM",
|
| 159 | [MEM_DDR4] = "Unbuffered-DDR4",
|
| 160 | [MEM_RDDR4] = "Registered-DDR4",
|
| 161 | [MEM_LPDDR4] = "Low-Power-DDR4-RAM",
|
| 162 | [MEM_LRDDR4] = "Load-Reduced-DDR4-RAM",
|
| 163 | [MEM_DDR5] = "Unbuffered-DDR5",
|
| 164 | [MEM_RDDR5] = "Registered-DDR5",
|
| 165 | [MEM_LRDDR5] = "Load-Reduced-DDR5-RAM",
|
| 166 | [MEM_NVDIMM] = "Non-volatile-RAM",
|
| 167 | [MEM_WIO2] = "Wide-IO-2",
|
| 168 | [MEM_HBM2] = "High-bandwidth-memory-Gen2",
|
| 169 | [MEM_HBM3] = "High-bandwidth-memory-Gen3",
|
| 170 | };
|
| 171 | EXPORT_SYMBOL_GPL(edac_mem_types);
|
| 172 |
|
| 173 | static void _edac_mc_free(struct mem_ctl_info *mci)
|
| 174 | {
|
| 175 | put_device(&mci->dev);
|
| 176 | }
|
| 177 |
|
| 178 | static void mci_release(struct device *dev)
|
| 179 | {
|
| 180 | struct mem_ctl_info *mci = container_of(dev, struct mem_ctl_info, dev);
|
| 181 | struct csrow_info *csr;
|
| 182 | int i, chn, row;
|
| 183 |
|
| 184 | if (mci->dimms) {
|
| 185 | for (i = 0; i < mci->tot_dimms; i++)
|
| 186 | kfree(mci->dimms[i]);
|
| 187 | kfree(mci->dimms);
|
| 188 | }
|
| 189 |
|
| 190 | if (mci->csrows) {
|
| 191 | for (row = 0; row < mci->nr_csrows; row++) {
|
| 192 | csr = mci->csrows[row];
|
| 193 | if (!csr)
|
| 194 | continue;
|
| 195 |
|
| 196 | if (csr->channels) {
|
| 197 | for (chn = 0; chn < mci->num_cschannel; chn++)
|
| 198 | kfree(csr->channels[chn]);
|
| 199 | kfree(csr->channels);
|
| 200 | }
|
| 201 | kfree(csr);
|
| 202 | }
|
| 203 | kfree(mci->csrows);
|
| 204 | }
|
| 205 | kfree(mci->pvt_info);
|
| 206 | kfree(mci);
|
| 207 | }
|
| 208 |
|
| 209 | static int edac_mc_alloc_csrows(struct mem_ctl_info *mci)
|
| 210 | {
|
| 211 | unsigned int tot_channels = mci->num_cschannel;
|
| 212 | unsigned int tot_csrows = mci->nr_csrows;
|
| 213 | unsigned int row, chn;
|
| 214 |
|
| 215 | |
| 216 | * Allocate and fill the csrow/channels structs
|
| 217 |
|
| 218 | mci->csrows = kzalloc_objs(*mci->csrows, tot_csrows);
|
| 219 | if (!mci->csrows)
|
| 220 | return -ENOMEM;
|
| 221 |
|
| 222 | for (row = 0; row < tot_csrows; row++) {
|
| 223 | struct csrow_info *csr;
|
| 224 |
|
| 225 | csr = kzalloc_obj(**mci->csrows);
|
| 226 | if (!csr)
|
| 227 | return -ENOMEM;
|
| 228 |
|
| 229 | mci->csrows[row] = csr;
|
| 230 | csr->csrow_idx = row;
|
| 231 | csr->mci = mci;
|
| 232 | csr->nr_channels = tot_channels;
|
| 233 | csr->channels = kzalloc_objs(*csr->channels, tot_channels);
|
| 234 | if (!csr->channels)
|
| 235 | return -ENOMEM;
|
| 236 |
|
| 237 | for (chn = 0; chn < tot_channels; chn++) {
|
| 238 | struct rank_info *chan;
|
| 239 |
|
| 240 | chan = kzalloc_obj(**csr->channels);
|
| 241 | if (!chan)
|
| 242 | return -ENOMEM;
|
| 243 |
|
| 244 | csr->channels[chn] = chan;
|
| 245 | chan->chan_idx = chn;
|
| 246 | chan->csrow = csr;
|
| 247 | }
|
| 248 | }
|
| 249 |
|
| 250 | return 0;
|
| 251 | }
|
| 252 |
|
| 253 | static int edac_mc_alloc_dimms(struct mem_ctl_info *mci)
|
| 254 | {
|
| 255 | unsigned int pos[EDAC_MAX_LAYERS];
|
| 256 | unsigned int row, chn, idx;
|
| 257 | int layer;
|
| 258 | void *p;
|
| 259 |
|
| 260 | |
| 261 | * Allocate and fill the dimm structs
|
| 262 |
|
| 263 | mci->dimms = kzalloc_objs(*mci->dimms, mci->tot_dimms);
|
| 264 | if (!mci->dimms)
|
| 265 | return -ENOMEM;
|
| 266 |
|
| 267 | memset(&pos, 0, sizeof(pos));
|
| 268 | row = 0;
|
| 269 | chn = 0;
|
| 270 | for (idx = 0; idx < mci->tot_dimms; idx++) {
|
| 271 | struct dimm_info *dimm;
|
| 272 | struct rank_info *chan;
|
| 273 | int n, len;
|
| 274 |
|
| 275 | chan = mci->csrows[row]->channels[chn];
|
| 276 |
|
| 277 | dimm = kzalloc_obj(**mci->dimms);
|
| 278 | if (!dimm)
|
| 279 | return -ENOMEM;
|
| 280 | mci->dimms[idx] = dimm;
|
| 281 | dimm->mci = mci;
|
| 282 | dimm->idx = idx;
|
| 283 |
|
| 284 | |
| 285 | * Copy DIMM location and initialize it.
|
| 286 |
|
| 287 | len = sizeof(dimm->label);
|
| 288 | p = dimm->label;
|
| 289 | n = scnprintf(p, len, "mc#%u", mci->mc_idx);
|
| 290 | p += n;
|
| 291 | len -= n;
|
| 292 | for (layer = 0; layer < mci->n_layers; layer++) {
|
| 293 | n = scnprintf(p, len, "%s#%u",
|
| 294 | edac_layer_name[mci->layers[layer].type],
|
| 295 | pos[layer]);
|
| 296 | p += n;
|
| 297 | len -= n;
|
| 298 | dimm->location[layer] = pos[layer];
|
| 299 | }
|
| 300 |
|
| 301 |
|
| 302 | chan->dimm = dimm;
|
| 303 | dimm->csrow = row;
|
| 304 | dimm->cschannel = chn;
|
| 305 |
|
| 306 |
|
| 307 | if (mci->layers[0].is_virt_csrow) {
|
| 308 | chn++;
|
| 309 | if (chn == mci->num_cschannel) {
|
| 310 | chn = 0;
|
| 311 | row++;
|
| 312 | }
|
| 313 | } else {
|
| 314 | row++;
|
| 315 | if (row == mci->nr_csrows) {
|
| 316 | row = 0;
|
| 317 | chn++;
|
| 318 | }
|
| 319 | }
|
| 320 |
|
| 321 |
|
| 322 | for (layer = mci->n_layers - 1; layer >= 0; layer--) {
|
| 323 | pos[layer]++;
|
| 324 | if (pos[layer] < mci->layers[layer].size)
|
| 325 | break;
|
| 326 | pos[layer] = 0;
|
| 327 | }
|
| 328 | }
|
| 329 |
|
| 330 | return 0;
|
| 331 | }
|
| 332 |
|
| 333 | struct mem_ctl_info *edac_mc_alloc(unsigned int mc_num,
|
| 334 | unsigned int n_layers,
|
| 335 | struct edac_mc_layer *layers,
|
| 336 | unsigned int sz_pvt)
|
| 337 | {
|
| 338 | struct mem_ctl_info *mci;
|
| 339 | struct edac_mc_layer *layer;
|
| 340 | unsigned int idx, tot_dimms = 1;
|
| 341 | unsigned int tot_csrows = 1, tot_channels = 1;
|
| 342 | bool per_rank = false;
|
| 343 |
|
| 344 | if (WARN_ON(n_layers > EDAC_MAX_LAYERS || n_layers == 0))
|
| 345 | return NULL;
|
| 346 |
|
| 347 | |
| 348 | * Calculate the total amount of dimms and csrows/cschannels while
|
| 349 | * in the old API emulation mode
|
| 350 |
|
| 351 | for (idx = 0; idx < n_layers; idx++) {
|
| 352 | tot_dimms *= layers[idx].size;
|
| 353 |
|
| 354 | if (layers[idx].is_virt_csrow)
|
| 355 | tot_csrows *= layers[idx].size;
|
| 356 | else
|
| 357 | tot_channels *= layers[idx].size;
|
| 358 |
|
| 359 | if (layers[idx].type == EDAC_MC_LAYER_CHIP_SELECT)
|
| 360 | per_rank = true;
|
| 361 | }
|
| 362 |
|
| 363 | mci = kzalloc_flex(*mci, layers, n_layers);
|
| 364 | if (!mci)
|
| 365 | return NULL;
|
| 366 |
|
| 367 | mci->n_layers = n_layers;
|
| 368 | memcpy(mci->layers, layers, sizeof(*layer) * n_layers);
|
| 369 |
|
| 370 | mci->dev.release = mci_release;
|
| 371 | device_initialize(&mci->dev);
|
| 372 |
|
| 373 | mci->pvt_info = kzalloc(sz_pvt, GFP_KERNEL);
|
| 374 | if (!mci->pvt_info)
|
| 375 | goto error;
|
| 376 |
|
| 377 |
|
| 378 | mci->mc_idx = mc_num;
|
| 379 | mci->tot_dimms = tot_dimms;
|
| 380 | mci->nr_csrows = tot_csrows;
|
| 381 | mci->num_cschannel = tot_channels;
|
| 382 | mci->csbased = per_rank;
|
| 383 |
|
| 384 | if (edac_mc_alloc_csrows(mci))
|
| 385 | goto error;
|
| 386 |
|
| 387 | if (edac_mc_alloc_dimms(mci))
|
| 388 | goto error;
|
| 389 |
|
| 390 | mci->op_state = OP_ALLOC;
|
| 391 |
|
| 392 | return mci;
|
| 393 |
|
| 394 | error:
|
| 395 | _edac_mc_free(mci);
|
| 396 |
|
| 397 | return NULL;
|
| 398 | }
|
| 399 | EXPORT_SYMBOL_GPL(edac_mc_alloc);
|
| 400 |
|
| 401 | void edac_mc_free(struct mem_ctl_info *mci)
|
| 402 | {
|
| 403 | edac_dbg(1, "\n");
|
| 404 |
|
| 405 | _edac_mc_free(mci);
|
| 406 | }
|
| 407 | EXPORT_SYMBOL_GPL(edac_mc_free);
|
| 408 |
|
| 409 | bool edac_has_mcs(void)
|
| 410 | {
|
| 411 | bool ret;
|
| 412 |
|
| 413 | mutex_lock(&mem_ctls_mutex);
|
| 414 |
|
| 415 | ret = list_empty(&mc_devices);
|
| 416 |
|
| 417 | mutex_unlock(&mem_ctls_mutex);
|
| 418 |
|
| 419 | return !ret;
|
| 420 | }
|
| 421 | EXPORT_SYMBOL_GPL(edac_has_mcs);
|
| 422 |
|
| 423 |
|
| 424 | static struct mem_ctl_info *__find_mci_by_dev(struct device *dev)
|
| 425 | {
|
| 426 | struct mem_ctl_info *mci;
|
| 427 | struct list_head *item;
|
| 428 |
|
| 429 | edac_dbg(3, "\n");
|
| 430 |
|
| 431 | list_for_each(item, &mc_devices) {
|
| 432 | mci = list_entry(item, struct mem_ctl_info, link);
|
| 433 |
|
| 434 | if (mci->pdev == dev)
|
| 435 | return mci;
|
| 436 | }
|
| 437 |
|
| 438 | return NULL;
|
| 439 | }
|
| 440 |
|
| 441 | |
| 442 | * find_mci_by_dev
|
| 443 | *
|
| 444 | * scan list of controllers looking for the one that manages
|
| 445 | * the 'dev' device
|
| 446 | * @dev: pointer to a struct device related with the MCI
|
| 447 |
|
| 448 | struct mem_ctl_info *find_mci_by_dev(struct device *dev)
|
| 449 | {
|
| 450 | struct mem_ctl_info *ret;
|
| 451 |
|
| 452 | mutex_lock(&mem_ctls_mutex);
|
| 453 | ret = __find_mci_by_dev(dev);
|
| 454 | mutex_unlock(&mem_ctls_mutex);
|
| 455 |
|
| 456 | return ret;
|
| 457 | }
|
| 458 | EXPORT_SYMBOL_GPL(find_mci_by_dev);
|
| 459 |
|
| 460 | |
| 461 | * edac_mc_workq_function
|
| 462 | * performs the operation scheduled by a workq request
|
| 463 |
|
| 464 | static void edac_mc_workq_function(struct work_struct *work_req)
|
| 465 | {
|
| 466 | struct delayed_work *d_work = to_delayed_work(work_req);
|
| 467 | struct mem_ctl_info *mci = to_edac_mem_ctl_work(d_work);
|
| 468 |
|
| 469 | mutex_lock(&mem_ctls_mutex);
|
| 470 |
|
| 471 | if (mci->op_state != OP_RUNNING_POLL) {
|
| 472 | mutex_unlock(&mem_ctls_mutex);
|
| 473 | return;
|
| 474 | }
|
| 475 |
|
| 476 | if (edac_op_state == EDAC_OPSTATE_POLL)
|
| 477 | mci->edac_check(mci);
|
| 478 |
|
| 479 | mutex_unlock(&mem_ctls_mutex);
|
| 480 |
|
| 481 |
|
| 482 | edac_queue_work(&mci->work, msecs_to_jiffies(edac_mc_get_poll_msec()));
|
| 483 | }
|
| 484 |
|
| 485 | |
| 486 | * edac_mc_reset_delay_period(unsigned long value)
|
| 487 | *
|
| 488 | * user space has updated our poll period value, need to
|
| 489 | * reset our workq delays
|
| 490 |
|
| 491 | void edac_mc_reset_delay_period(unsigned long value)
|
| 492 | {
|
| 493 | struct mem_ctl_info *mci;
|
| 494 | struct list_head *item;
|
| 495 |
|
| 496 | mutex_lock(&mem_ctls_mutex);
|
| 497 |
|
| 498 | list_for_each(item, &mc_devices) {
|
| 499 | mci = list_entry(item, struct mem_ctl_info, link);
|
| 500 |
|
| 501 | if (mci->op_state == OP_RUNNING_POLL)
|
| 502 | edac_mod_work(&mci->work, value);
|
| 503 | }
|
| 504 | mutex_unlock(&mem_ctls_mutex);
|
| 505 | }
|
| 506 |
|
| 507 |
|
| 508 |
|
| 509 | |
| 510 | * Before calling this function, caller must
|
| 511 | * assign a unique value to mci->mc_idx.
|
| 512 | *
|
| 513 | * locking model:
|
| 514 | *
|
| 515 | * called with the mem_ctls_mutex lock held
|
| 516 |
|
| 517 | static int add_mc_to_global_list(struct mem_ctl_info *mci)
|
| 518 | {
|
| 519 | struct list_head *item, *insert_before;
|
| 520 | struct mem_ctl_info *p;
|
| 521 |
|
| 522 | insert_before = &mc_devices;
|
| 523 |
|
| 524 | p = __find_mci_by_dev(mci->pdev);
|
| 525 | if (unlikely(p != NULL))
|
| 526 | goto fail0;
|
| 527 |
|
| 528 | list_for_each(item, &mc_devices) {
|
| 529 | p = list_entry(item, struct mem_ctl_info, link);
|
| 530 |
|
| 531 | if (p->mc_idx >= mci->mc_idx) {
|
| 532 | if (unlikely(p->mc_idx == mci->mc_idx))
|
| 533 | goto fail1;
|
| 534 |
|
| 535 | insert_before = item;
|
| 536 | break;
|
| 537 | }
|
| 538 | }
|
| 539 |
|
| 540 | list_add_tail_rcu(&mci->link, insert_before);
|
| 541 | return 0;
|
| 542 |
|
| 543 | fail0:
|
| 544 | edac_printk(KERN_WARNING, EDAC_MC,
|
| 545 | "%s (%s) %s %s already assigned %d\n", dev_name(p->pdev),
|
| 546 | edac_dev_name(mci), p->mod_name, p->ctl_name, p->mc_idx);
|
| 547 | return 1;
|
| 548 |
|
| 549 | fail1:
|
| 550 | edac_printk(KERN_WARNING, EDAC_MC,
|
| 551 | "bug in low-level driver: attempt to assign\n"
|
| 552 | " duplicate mc_idx %d in %s()\n", p->mc_idx, __func__);
|
| 553 | return 1;
|
| 554 | }
|
| 555 |
|
| 556 | static int del_mc_from_global_list(struct mem_ctl_info *mci)
|
| 557 | {
|
| 558 | list_del_rcu(&mci->link);
|
| 559 |
|
| 560 | |
| 561 | * NMI handlers may be traversing list
|
| 562 |
|
| 563 | synchronize_rcu();
|
| 564 | INIT_LIST_HEAD(&mci->link);
|
| 565 |
|
| 566 | return list_empty(&mc_devices);
|
| 567 | }
|
| 568 |
|
| 569 | struct mem_ctl_info *edac_mc_find(int idx)
|
| 570 | {
|
| 571 | struct mem_ctl_info *mci;
|
| 572 | struct list_head *item;
|
| 573 |
|
| 574 | mutex_lock(&mem_ctls_mutex);
|
| 575 |
|
| 576 | list_for_each(item, &mc_devices) {
|
| 577 | mci = list_entry(item, struct mem_ctl_info, link);
|
| 578 | if (mci->mc_idx == idx)
|
| 579 | goto unlock;
|
| 580 | }
|
| 581 |
|
| 582 | mci = NULL;
|
| 583 | unlock:
|
| 584 | mutex_unlock(&mem_ctls_mutex);
|
| 585 | return mci;
|
| 586 | }
|
| 587 | EXPORT_SYMBOL(edac_mc_find);
|
| 588 |
|
| 589 | const char *edac_get_owner(void)
|
| 590 | {
|
| 591 | return edac_mc_owner;
|
| 592 | }
|
| 593 | EXPORT_SYMBOL_GPL(edac_get_owner);
|
| 594 |
|
| 595 |
|
| 596 | int edac_mc_add_mc_with_groups(struct mem_ctl_info *mci,
|
| 597 | const struct attribute_group **groups)
|
| 598 | {
|
| 599 | int ret = -EINVAL;
|
| 600 | edac_dbg(0, "\n");
|
| 601 |
|
| 602 | #ifdef CONFIG_EDAC_DEBUG
|
| 603 | if (edac_debug_level >= 3)
|
| 604 | edac_mc_dump_mci(mci);
|
| 605 |
|
| 606 | if (edac_debug_level >= 4) {
|
| 607 | struct dimm_info *dimm;
|
| 608 | int i;
|
| 609 |
|
| 610 | for (i = 0; i < mci->nr_csrows; i++) {
|
| 611 | struct csrow_info *csrow = mci->csrows[i];
|
| 612 | u32 nr_pages = 0;
|
| 613 | int j;
|
| 614 |
|
| 615 | for (j = 0; j < csrow->nr_channels; j++)
|
| 616 | nr_pages += csrow->channels[j]->dimm->nr_pages;
|
| 617 | if (!nr_pages)
|
| 618 | continue;
|
| 619 | edac_mc_dump_csrow(csrow);
|
| 620 | for (j = 0; j < csrow->nr_channels; j++)
|
| 621 | if (csrow->channels[j]->dimm->nr_pages)
|
| 622 | edac_mc_dump_channel(csrow->channels[j]);
|
| 623 | }
|
| 624 |
|
| 625 | mci_for_each_dimm(mci, dimm)
|
| 626 | edac_mc_dump_dimm(dimm);
|
| 627 | }
|
| 628 | #endif
|
| 629 | mutex_lock(&mem_ctls_mutex);
|
| 630 |
|
| 631 | if (edac_mc_owner && edac_mc_owner != mci->mod_name) {
|
| 632 | ret = -EPERM;
|
| 633 | goto fail0;
|
| 634 | }
|
| 635 |
|
| 636 | if (add_mc_to_global_list(mci))
|
| 637 | goto fail0;
|
| 638 |
|
| 639 |
|
| 640 | mci->start_time = jiffies;
|
| 641 |
|
| 642 | mci->bus = edac_get_sysfs_subsys();
|
| 643 |
|
| 644 | if (edac_create_sysfs_mci_device(mci, groups)) {
|
| 645 | edac_mc_printk(mci, KERN_WARNING,
|
| 646 | "failed to create sysfs device\n");
|
| 647 | goto fail1;
|
| 648 | }
|
| 649 |
|
| 650 | if (mci->edac_check) {
|
| 651 | mci->op_state = OP_RUNNING_POLL;
|
| 652 |
|
| 653 | INIT_DELAYED_WORK(&mci->work, edac_mc_workq_function);
|
| 654 | edac_queue_work(&mci->work, msecs_to_jiffies(edac_mc_get_poll_msec()));
|
| 655 |
|
| 656 | } else {
|
| 657 | mci->op_state = OP_RUNNING_INTERRUPT;
|
| 658 | }
|
| 659 |
|
| 660 |
|
| 661 | edac_mc_printk(mci, KERN_INFO,
|
| 662 | "Giving out device to module %s controller %s: DEV %s (%s)\n",
|
| 663 | mci->mod_name, mci->ctl_name, mci->dev_name,
|
| 664 | edac_op_state_to_string(mci->op_state));
|
| 665 |
|
| 666 | edac_mc_owner = mci->mod_name;
|
| 667 |
|
| 668 | mutex_unlock(&mem_ctls_mutex);
|
| 669 | return 0;
|
| 670 |
|
| 671 | fail1:
|
| 672 | del_mc_from_global_list(mci);
|
| 673 |
|
| 674 | fail0:
|
| 675 | mutex_unlock(&mem_ctls_mutex);
|
| 676 | return ret;
|
| 677 | }
|
| 678 | EXPORT_SYMBOL_GPL(edac_mc_add_mc_with_groups);
|
| 679 |
|
| 680 | struct mem_ctl_info *edac_mc_del_mc(struct device *dev)
|
| 681 | {
|
| 682 | struct mem_ctl_info *mci;
|
| 683 |
|
| 684 | edac_dbg(0, "\n");
|
| 685 |
|
| 686 | mutex_lock(&mem_ctls_mutex);
|
| 687 |
|
| 688 |
|
| 689 | mci = __find_mci_by_dev(dev);
|
| 690 | if (mci == NULL) {
|
| 691 | mutex_unlock(&mem_ctls_mutex);
|
| 692 | return NULL;
|
| 693 | }
|
| 694 |
|
| 695 |
|
| 696 | mci->op_state = OP_OFFLINE;
|
| 697 |
|
| 698 | if (del_mc_from_global_list(mci))
|
| 699 | edac_mc_owner = NULL;
|
| 700 |
|
| 701 | mutex_unlock(&mem_ctls_mutex);
|
| 702 |
|
| 703 | if (mci->edac_check)
|
| 704 | edac_stop_work(&mci->work);
|
| 705 |
|
| 706 |
|
| 707 | edac_remove_sysfs_mci_device(mci);
|
| 708 |
|
| 709 | edac_printk(KERN_INFO, EDAC_MC,
|
| 710 | "Removed device %d for %s %s: DEV %s\n", mci->mc_idx,
|
| 711 | mci->mod_name, mci->ctl_name, edac_dev_name(mci));
|
| 712 |
|
| 713 | return mci;
|
| 714 | }
|
| 715 | EXPORT_SYMBOL_GPL(edac_mc_del_mc);
|
| 716 |
|
| 717 | static void edac_mc_scrub_block(unsigned long page, unsigned long offset,
|
| 718 | u32 size)
|
| 719 | {
|
| 720 | struct page *pg;
|
| 721 | void *virt_addr;
|
| 722 | unsigned long flags = 0;
|
| 723 |
|
| 724 | edac_dbg(3, "\n");
|
| 725 |
|
| 726 |
|
| 727 | if (!pfn_valid(page))
|
| 728 | return;
|
| 729 |
|
| 730 |
|
| 731 | pg = pfn_to_page(page);
|
| 732 |
|
| 733 | if (PageHighMem(pg))
|
| 734 | local_irq_save(flags);
|
| 735 |
|
| 736 | virt_addr = kmap_atomic(pg);
|
| 737 |
|
| 738 |
|
| 739 | edac_atomic_scrub(virt_addr + offset, size);
|
| 740 |
|
| 741 |
|
| 742 | kunmap_atomic(virt_addr);
|
| 743 |
|
| 744 | if (PageHighMem(pg))
|
| 745 | local_irq_restore(flags);
|
| 746 | }
|
| 747 |
|
| 748 |
|
| 749 | int edac_mc_find_csrow_by_page(struct mem_ctl_info *mci, unsigned long page)
|
| 750 | {
|
| 751 | struct csrow_info **csrows = mci->csrows;
|
| 752 | int row, i, j, n;
|
| 753 |
|
| 754 | edac_dbg(1, "MC%d: 0x%lx\n", mci->mc_idx, page);
|
| 755 | row = -1;
|
| 756 |
|
| 757 | for (i = 0; i < mci->nr_csrows; i++) {
|
| 758 | struct csrow_info *csrow = csrows[i];
|
| 759 | n = 0;
|
| 760 | for (j = 0; j < csrow->nr_channels; j++) {
|
| 761 | struct dimm_info *dimm = csrow->channels[j]->dimm;
|
| 762 | n += dimm->nr_pages;
|
| 763 | }
|
| 764 | if (n == 0)
|
| 765 | continue;
|
| 766 |
|
| 767 | edac_dbg(3, "MC%d: first(0x%lx) page(0x%lx) last(0x%lx) mask(0x%lx)\n",
|
| 768 | mci->mc_idx,
|
| 769 | csrow->first_page, page, csrow->last_page,
|
| 770 | csrow->page_mask);
|
| 771 |
|
| 772 | if ((page >= csrow->first_page) &&
|
| 773 | (page <= csrow->last_page) &&
|
| 774 | ((page & csrow->page_mask) ==
|
| 775 | (csrow->first_page & csrow->page_mask))) {
|
| 776 | row = i;
|
| 777 | break;
|
| 778 | }
|
| 779 | }
|
| 780 |
|
| 781 | if (row == -1)
|
| 782 | edac_mc_printk(mci, KERN_ERR,
|
| 783 | "could not look up page error address %lx\n",
|
| 784 | (unsigned long)page);
|
| 785 |
|
| 786 | return row;
|
| 787 | }
|
| 788 | EXPORT_SYMBOL_GPL(edac_mc_find_csrow_by_page);
|
| 789 |
|
| 790 | const char *edac_layer_name[] = {
|
| 791 | [EDAC_MC_LAYER_BRANCH] = "branch",
|
| 792 | [EDAC_MC_LAYER_CHANNEL] = "channel",
|
| 793 | [EDAC_MC_LAYER_SLOT] = "slot",
|
| 794 | [EDAC_MC_LAYER_CHIP_SELECT] = "csrow",
|
| 795 | [EDAC_MC_LAYER_ALL_MEM] = "memory",
|
| 796 | };
|
| 797 | EXPORT_SYMBOL_GPL(edac_layer_name);
|
| 798 |
|
| 799 | static void edac_inc_ce_error(struct edac_raw_error_desc *e)
|
| 800 | {
|
| 801 | int pos[EDAC_MAX_LAYERS] = { e->top_layer, e->mid_layer, e->low_layer };
|
| 802 | struct mem_ctl_info *mci = error_desc_to_mci(e);
|
| 803 | struct dimm_info *dimm = edac_get_dimm(mci, pos[0], pos[1], pos[2]);
|
| 804 |
|
| 805 | mci->ce_mc += e->error_count;
|
| 806 |
|
| 807 | if (dimm)
|
| 808 | dimm->ce_count += e->error_count;
|
| 809 | else
|
| 810 | mci->ce_noinfo_count += e->error_count;
|
| 811 | }
|
| 812 |
|
| 813 | static void edac_inc_ue_error(struct edac_raw_error_desc *e)
|
| 814 | {
|
| 815 | int pos[EDAC_MAX_LAYERS] = { e->top_layer, e->mid_layer, e->low_layer };
|
| 816 | struct mem_ctl_info *mci = error_desc_to_mci(e);
|
| 817 | struct dimm_info *dimm = edac_get_dimm(mci, pos[0], pos[1], pos[2]);
|
| 818 |
|
| 819 | mci->ue_mc += e->error_count;
|
| 820 |
|
| 821 | if (dimm)
|
| 822 | dimm->ue_count += e->error_count;
|
| 823 | else
|
| 824 | mci->ue_noinfo_count += e->error_count;
|
| 825 | }
|
| 826 |
|
| 827 | static void edac_ce_error(struct edac_raw_error_desc *e)
|
| 828 | {
|
| 829 | struct mem_ctl_info *mci = error_desc_to_mci(e);
|
| 830 | unsigned long remapped_page;
|
| 831 |
|
| 832 | if (edac_mc_get_log_ce()) {
|
| 833 | edac_mc_printk(mci, KERN_WARNING,
|
| 834 | "%d CE %s%son %s (%s page:0x%lx offset:0x%lx grain:%ld syndrome:0x%lx%s%s)\n",
|
| 835 | e->error_count, e->msg,
|
| 836 | *e->msg ? " " : "",
|
| 837 | e->label, e->location, e->page_frame_number, e->offset_in_page,
|
| 838 | e->grain, e->syndrome,
|
| 839 | *e->other_detail ? " - " : "",
|
| 840 | e->other_detail);
|
| 841 | }
|
| 842 |
|
| 843 | edac_inc_ce_error(e);
|
| 844 |
|
| 845 | if (mci->scrub_mode == SCRUB_SW_SRC) {
|
| 846 | |
| 847 | * Some memory controllers (called MCs below) can remap
|
| 848 | * memory so that it is still available at a different
|
| 849 | * address when PCI devices map into memory.
|
| 850 | * MC's that can't do this, lose the memory where PCI
|
| 851 | * devices are mapped. This mapping is MC-dependent
|
| 852 | * and so we call back into the MC driver for it to
|
| 853 | * map the MC page to a physical (CPU) page which can
|
| 854 | * then be mapped to a virtual page - which can then
|
| 855 | * be scrubbed.
|
| 856 |
|
| 857 | remapped_page = mci->ctl_page_to_phys ?
|
| 858 | mci->ctl_page_to_phys(mci, e->page_frame_number) :
|
| 859 | e->page_frame_number;
|
| 860 |
|
| 861 | edac_mc_scrub_block(remapped_page, e->offset_in_page, e->grain);
|
| 862 | }
|
| 863 | }
|
| 864 |
|
| 865 | static void edac_ue_error(struct edac_raw_error_desc *e)
|
| 866 | {
|
| 867 | struct mem_ctl_info *mci = error_desc_to_mci(e);
|
| 868 |
|
| 869 | if (edac_mc_get_log_ue()) {
|
| 870 | edac_mc_printk(mci, KERN_WARNING,
|
| 871 | "%d UE %s%son %s (%s page:0x%lx offset:0x%lx grain:%ld%s%s)\n",
|
| 872 | e->error_count, e->msg,
|
| 873 | *e->msg ? " " : "",
|
| 874 | e->label, e->location, e->page_frame_number, e->offset_in_page,
|
| 875 | e->grain,
|
| 876 | *e->other_detail ? " - " : "",
|
| 877 | e->other_detail);
|
| 878 | }
|
| 879 |
|
| 880 | edac_inc_ue_error(e);
|
| 881 |
|
| 882 | if (edac_mc_get_panic_on_ue()) {
|
| 883 | panic("UE %s%son %s (%s page:0x%lx offset:0x%lx grain:%ld%s%s)\n",
|
| 884 | e->msg,
|
| 885 | *e->msg ? " " : "",
|
| 886 | e->label, e->location, e->page_frame_number, e->offset_in_page,
|
| 887 | e->grain,
|
| 888 | *e->other_detail ? " - " : "",
|
| 889 | e->other_detail);
|
| 890 | }
|
| 891 | }
|
| 892 |
|
| 893 | static void edac_inc_csrow(struct edac_raw_error_desc *e, int row, int chan)
|
| 894 | {
|
| 895 | struct mem_ctl_info *mci = error_desc_to_mci(e);
|
| 896 | enum hw_event_mc_err_type type = e->type;
|
| 897 | u16 count = e->error_count;
|
| 898 |
|
| 899 | if (row < 0)
|
| 900 | return;
|
| 901 |
|
| 902 | edac_dbg(4, "csrow/channel to increment: (%d,%d)\n", row, chan);
|
| 903 |
|
| 904 | if (type == HW_EVENT_ERR_CORRECTED) {
|
| 905 | mci->csrows[row]->ce_count += count;
|
| 906 | if (chan >= 0)
|
| 907 | mci->csrows[row]->channels[chan]->ce_count += count;
|
| 908 | } else {
|
| 909 | mci->csrows[row]->ue_count += count;
|
| 910 | }
|
| 911 | }
|
| 912 |
|
| 913 | void edac_raw_mc_handle_error(struct edac_raw_error_desc *e)
|
| 914 | {
|
| 915 | struct mem_ctl_info *mci = error_desc_to_mci(e);
|
| 916 | u8 grain_bits;
|
| 917 |
|
| 918 |
|
| 919 | if (WARN_ON_ONCE(!e->grain))
|
| 920 | e->grain = 1;
|
| 921 |
|
| 922 | grain_bits = fls_long(e->grain - 1);
|
| 923 |
|
| 924 |
|
| 925 | if (IS_ENABLED(CONFIG_RAS))
|
| 926 | trace_mc_event(e->type, e->msg, e->label, e->error_count,
|
| 927 | mci->mc_idx, e->top_layer, e->mid_layer,
|
| 928 | e->low_layer,
|
| 929 | (e->page_frame_number << PAGE_SHIFT) | e->offset_in_page,
|
| 930 | grain_bits, e->syndrome, e->other_detail);
|
| 931 |
|
| 932 | if (e->type == HW_EVENT_ERR_CORRECTED)
|
| 933 | edac_ce_error(e);
|
| 934 | else
|
| 935 | edac_ue_error(e);
|
| 936 | }
|
| 937 | EXPORT_SYMBOL_GPL(edac_raw_mc_handle_error);
|
| 938 |
|
| 939 | void edac_mc_handle_error(const enum hw_event_mc_err_type type,
|
| 940 | struct mem_ctl_info *mci,
|
| 941 | const u16 error_count,
|
| 942 | const unsigned long page_frame_number,
|
| 943 | const unsigned long offset_in_page,
|
| 944 | const unsigned long syndrome,
|
| 945 | const int top_layer,
|
| 946 | const int mid_layer,
|
| 947 | const int low_layer,
|
| 948 | const char *msg,
|
| 949 | const char *other_detail)
|
| 950 | {
|
| 951 | struct dimm_info *dimm;
|
| 952 | char *p, *end;
|
| 953 | int row = -1, chan = -1;
|
| 954 | int pos[EDAC_MAX_LAYERS] = { top_layer, mid_layer, low_layer };
|
| 955 | int i, n_labels = 0;
|
| 956 | struct edac_raw_error_desc *e = &mci->error_desc;
|
| 957 | bool any_memory = true;
|
| 958 | const char *prefix;
|
| 959 |
|
| 960 | edac_dbg(3, "MC%d\n", mci->mc_idx);
|
| 961 |
|
| 962 |
|
| 963 | memset(e, 0, sizeof (*e));
|
| 964 | e->error_count = error_count;
|
| 965 | e->type = type;
|
| 966 | e->top_layer = top_layer;
|
| 967 | e->mid_layer = mid_layer;
|
| 968 | e->low_layer = low_layer;
|
| 969 | e->page_frame_number = page_frame_number;
|
| 970 | e->offset_in_page = offset_in_page;
|
| 971 | e->syndrome = syndrome;
|
| 972 |
|
| 973 | e->msg = msg ?: "";
|
| 974 | e->other_detail = other_detail ?: "";
|
| 975 |
|
| 976 | |
| 977 | * Check if the event report is consistent and if the memory location is
|
| 978 | * known. If it is, the DIMM(s) label info will be filled and the DIMM's
|
| 979 | * error counters will be incremented.
|
| 980 |
|
| 981 | for (i = 0; i < mci->n_layers; i++) {
|
| 982 | if (pos[i] >= (int)mci->layers[i].size) {
|
| 983 |
|
| 984 | edac_mc_printk(mci, KERN_ERR,
|
| 985 | "INTERNAL ERROR: %s value is out of range (%d >= %d)\n",
|
| 986 | edac_layer_name[mci->layers[i].type],
|
| 987 | pos[i], mci->layers[i].size);
|
| 988 | |
| 989 | * Instead of just returning it, let's use what's
|
| 990 | * known about the error. The increment routines and
|
| 991 | * the DIMM filter logic will do the right thing by
|
| 992 | * pointing the likely damaged DIMMs.
|
| 993 |
|
| 994 | pos[i] = -1;
|
| 995 | }
|
| 996 | if (pos[i] >= 0)
|
| 997 | any_memory = false;
|
| 998 | }
|
| 999 |
|
| 1000 | |
| 1001 | * Get the dimm label/grain that applies to the match criteria.
|
| 1002 | * As the error algorithm may not be able to point to just one memory
|
| 1003 | * stick, the logic here will get all possible labels that could
|
| 1004 | * pottentially be affected by the error.
|
| 1005 | * On FB-DIMM memory controllers, for uncorrected errors, it is common
|
| 1006 | * to have only the MC channel and the MC dimm (also called "branch")
|
| 1007 | * but the channel is not known, as the memory is arranged in pairs,
|
| 1008 | * where each memory belongs to a separate channel within the same
|
| 1009 | * branch.
|
| 1010 |
|
| 1011 | p = e->label;
|
| 1012 | *p = '\0';
|
| 1013 | end = p + sizeof(e->label);
|
| 1014 | prefix = "";
|
| 1015 |
|
| 1016 | mci_for_each_dimm(mci, dimm) {
|
| 1017 | if (top_layer >= 0 && top_layer != dimm->location[0])
|
| 1018 | continue;
|
| 1019 | if (mid_layer >= 0 && mid_layer != dimm->location[1])
|
| 1020 | continue;
|
| 1021 | if (low_layer >= 0 && low_layer != dimm->location[2])
|
| 1022 | continue;
|
| 1023 |
|
| 1024 |
|
| 1025 | if (dimm->grain > e->grain)
|
| 1026 | e->grain = dimm->grain;
|
| 1027 |
|
| 1028 | |
| 1029 | * If the error is memory-controller wide, there's no need to
|
| 1030 | * seek for the affected DIMMs because the whole channel/memory
|
| 1031 | * controller/... may be affected. Also, don't show errors for
|
| 1032 | * empty DIMM slots.
|
| 1033 |
|
| 1034 | if (!dimm->nr_pages)
|
| 1035 | continue;
|
| 1036 |
|
| 1037 | n_labels++;
|
| 1038 | if (n_labels > EDAC_MAX_LABELS) {
|
| 1039 | p = e->label;
|
| 1040 | *p = '\0';
|
| 1041 | } else {
|
| 1042 | p += scnprintf(p, end - p, "%s%s", prefix, dimm->label);
|
| 1043 | prefix = OTHER_LABEL;
|
| 1044 | }
|
| 1045 |
|
| 1046 | |
| 1047 | * get csrow/channel of the DIMM, in order to allow
|
| 1048 | * incrementing the compat API counters
|
| 1049 |
|
| 1050 | edac_dbg(4, "%s csrows map: (%d,%d)\n",
|
| 1051 | mci->csbased ? "rank" : "dimm",
|
| 1052 | dimm->csrow, dimm->cschannel);
|
| 1053 | if (row == -1)
|
| 1054 | row = dimm->csrow;
|
| 1055 | else if (row >= 0 && row != dimm->csrow)
|
| 1056 | row = -2;
|
| 1057 |
|
| 1058 | if (chan == -1)
|
| 1059 | chan = dimm->cschannel;
|
| 1060 | else if (chan >= 0 && chan != dimm->cschannel)
|
| 1061 | chan = -2;
|
| 1062 | }
|
| 1063 |
|
| 1064 | if (any_memory)
|
| 1065 | strscpy(e->label, "any memory", sizeof(e->label));
|
| 1066 | else if (!*e->label)
|
| 1067 | strscpy(e->label, "unknown memory", sizeof(e->label));
|
| 1068 |
|
| 1069 | edac_inc_csrow(e, row, chan);
|
| 1070 |
|
| 1071 |
|
| 1072 | p = e->location;
|
| 1073 | end = p + sizeof(e->location);
|
| 1074 | prefix = "";
|
| 1075 |
|
| 1076 | for (i = 0; i < mci->n_layers; i++) {
|
| 1077 | if (pos[i] < 0)
|
| 1078 | continue;
|
| 1079 |
|
| 1080 | p += scnprintf(p, end - p, "%s%s:%d", prefix,
|
| 1081 | edac_layer_name[mci->layers[i].type], pos[i]);
|
| 1082 | prefix = " ";
|
| 1083 | }
|
| 1084 |
|
| 1085 | edac_raw_mc_handle_error(e);
|
| 1086 | }
|
| 1087 | EXPORT_SYMBOL_GPL(edac_mc_handle_error);
|