1699ea521SShiju Jose // SPDX-License-Identifier: GPL-2.0
2699ea521SShiju Jose /*
3699ea521SShiju Jose * The generic EDAC memory repair driver is designed to control the memory
4699ea521SShiju Jose * devices with memory repair features, such as Post Package Repair (PPR),
5699ea521SShiju Jose * memory sparing etc. The common sysfs memory repair interface abstracts
6699ea521SShiju Jose * the control of various arbitrary memory repair functionalities into a
7699ea521SShiju Jose * unified set of functions.
8699ea521SShiju Jose *
9699ea521SShiju Jose * Copyright (c) 2024-2025 HiSilicon Limited.
10699ea521SShiju Jose */
11699ea521SShiju Jose
12699ea521SShiju Jose #include <linux/edac.h>
13699ea521SShiju Jose
14699ea521SShiju Jose enum edac_mem_repair_attributes {
15699ea521SShiju Jose MR_TYPE,
16699ea521SShiju Jose MR_PERSIST_MODE,
17699ea521SShiju Jose MR_SAFE_IN_USE,
18699ea521SShiju Jose MR_HPA,
19699ea521SShiju Jose MR_MIN_HPA,
20699ea521SShiju Jose MR_MAX_HPA,
21699ea521SShiju Jose MR_DPA,
22699ea521SShiju Jose MR_MIN_DPA,
23699ea521SShiju Jose MR_MAX_DPA,
24699ea521SShiju Jose MR_NIBBLE_MASK,
2581e42fc1SShiju Jose MR_BANK_GROUP,
2681e42fc1SShiju Jose MR_BANK,
2781e42fc1SShiju Jose MR_RANK,
2881e42fc1SShiju Jose MR_ROW,
2981e42fc1SShiju Jose MR_COLUMN,
3081e42fc1SShiju Jose MR_CHANNEL,
3181e42fc1SShiju Jose MR_SUB_CHANNEL,
32699ea521SShiju Jose MEM_DO_REPAIR,
33699ea521SShiju Jose MR_MAX_ATTRS
34699ea521SShiju Jose };
35699ea521SShiju Jose
36699ea521SShiju Jose struct edac_mem_repair_dev_attr {
37699ea521SShiju Jose struct device_attribute dev_attr;
38699ea521SShiju Jose u8 instance;
39699ea521SShiju Jose };
40699ea521SShiju Jose
41699ea521SShiju Jose struct edac_mem_repair_context {
42699ea521SShiju Jose char name[EDAC_FEAT_NAME_LEN];
43699ea521SShiju Jose struct edac_mem_repair_dev_attr mem_repair_dev_attr[MR_MAX_ATTRS];
44699ea521SShiju Jose struct attribute *mem_repair_attrs[MR_MAX_ATTRS + 1];
45699ea521SShiju Jose struct attribute_group group;
46699ea521SShiju Jose };
47699ea521SShiju Jose
48588ca944SShiju Jose const char * const edac_repair_type[] = {
49588ca944SShiju Jose [EDAC_REPAIR_PPR] = "ppr",
50588ca944SShiju Jose [EDAC_REPAIR_CACHELINE_SPARING] = "cacheline-sparing",
51588ca944SShiju Jose [EDAC_REPAIR_ROW_SPARING] = "row-sparing",
52588ca944SShiju Jose [EDAC_REPAIR_BANK_SPARING] = "bank-sparing",
53588ca944SShiju Jose [EDAC_REPAIR_RANK_SPARING] = "rank-sparing",
54588ca944SShiju Jose };
55588ca944SShiju Jose EXPORT_SYMBOL_GPL(edac_repair_type);
56588ca944SShiju Jose
57699ea521SShiju Jose #define TO_MR_DEV_ATTR(_dev_attr) \
58699ea521SShiju Jose container_of(_dev_attr, struct edac_mem_repair_dev_attr, dev_attr)
59699ea521SShiju Jose
60699ea521SShiju Jose #define MR_ATTR_SHOW(attrib, cb, type, format) \
61699ea521SShiju Jose static ssize_t attrib##_show(struct device *ras_feat_dev, \
62699ea521SShiju Jose struct device_attribute *attr, char *buf) \
63699ea521SShiju Jose { \
64699ea521SShiju Jose u8 inst = TO_MR_DEV_ATTR(attr)->instance; \
65699ea521SShiju Jose struct edac_dev_feat_ctx *ctx = dev_get_drvdata(ras_feat_dev); \
66699ea521SShiju Jose const struct edac_mem_repair_ops *ops = \
67699ea521SShiju Jose ctx->mem_repair[inst].mem_repair_ops; \
68699ea521SShiju Jose type data; \
69699ea521SShiju Jose int ret; \
70699ea521SShiju Jose \
71699ea521SShiju Jose ret = ops->cb(ras_feat_dev->parent, ctx->mem_repair[inst].private, \
72699ea521SShiju Jose &data); \
73699ea521SShiju Jose if (ret) \
74699ea521SShiju Jose return ret; \
75699ea521SShiju Jose \
76699ea521SShiju Jose return sysfs_emit(buf, format, data); \
77699ea521SShiju Jose }
78699ea521SShiju Jose
79699ea521SShiju Jose MR_ATTR_SHOW(repair_type, get_repair_type, const char *, "%s\n")
80699ea521SShiju Jose MR_ATTR_SHOW(persist_mode, get_persist_mode, bool, "%u\n")
81699ea521SShiju Jose MR_ATTR_SHOW(repair_safe_when_in_use, get_repair_safe_when_in_use, bool, "%u\n")
82699ea521SShiju Jose MR_ATTR_SHOW(hpa, get_hpa, u64, "0x%llx\n")
83699ea521SShiju Jose MR_ATTR_SHOW(min_hpa, get_min_hpa, u64, "0x%llx\n")
84699ea521SShiju Jose MR_ATTR_SHOW(max_hpa, get_max_hpa, u64, "0x%llx\n")
85699ea521SShiju Jose MR_ATTR_SHOW(dpa, get_dpa, u64, "0x%llx\n")
86699ea521SShiju Jose MR_ATTR_SHOW(min_dpa, get_min_dpa, u64, "0x%llx\n")
87699ea521SShiju Jose MR_ATTR_SHOW(max_dpa, get_max_dpa, u64, "0x%llx\n")
88699ea521SShiju Jose MR_ATTR_SHOW(nibble_mask, get_nibble_mask, u32, "0x%x\n")
8981e42fc1SShiju Jose MR_ATTR_SHOW(bank_group, get_bank_group, u32, "%u\n")
9081e42fc1SShiju Jose MR_ATTR_SHOW(bank, get_bank, u32, "%u\n")
9181e42fc1SShiju Jose MR_ATTR_SHOW(rank, get_rank, u32, "%u\n")
9281e42fc1SShiju Jose MR_ATTR_SHOW(row, get_row, u32, "0x%x\n")
9381e42fc1SShiju Jose MR_ATTR_SHOW(column, get_column, u32, "%u\n")
9481e42fc1SShiju Jose MR_ATTR_SHOW(channel, get_channel, u32, "%u\n")
9581e42fc1SShiju Jose MR_ATTR_SHOW(sub_channel, get_sub_channel, u32, "%u\n")
96699ea521SShiju Jose
97699ea521SShiju Jose #define MR_ATTR_STORE(attrib, cb, type, conv_func) \
98699ea521SShiju Jose static ssize_t attrib##_store(struct device *ras_feat_dev, \
99699ea521SShiju Jose struct device_attribute *attr, \
100699ea521SShiju Jose const char *buf, size_t len) \
101699ea521SShiju Jose { \
102699ea521SShiju Jose u8 inst = TO_MR_DEV_ATTR(attr)->instance; \
103699ea521SShiju Jose struct edac_dev_feat_ctx *ctx = dev_get_drvdata(ras_feat_dev); \
104699ea521SShiju Jose const struct edac_mem_repair_ops *ops = \
105699ea521SShiju Jose ctx->mem_repair[inst].mem_repair_ops; \
106699ea521SShiju Jose type data; \
107699ea521SShiju Jose int ret; \
108699ea521SShiju Jose \
109699ea521SShiju Jose ret = conv_func(buf, 0, &data); \
110699ea521SShiju Jose if (ret < 0) \
111699ea521SShiju Jose return ret; \
112699ea521SShiju Jose \
113699ea521SShiju Jose ret = ops->cb(ras_feat_dev->parent, ctx->mem_repair[inst].private, \
114699ea521SShiju Jose data); \
115699ea521SShiju Jose if (ret) \
116699ea521SShiju Jose return ret; \
117699ea521SShiju Jose \
118699ea521SShiju Jose return len; \
119699ea521SShiju Jose }
120699ea521SShiju Jose
MR_ATTR_STORE(persist_mode,set_persist_mode,unsigned long,kstrtoul)121699ea521SShiju Jose MR_ATTR_STORE(persist_mode, set_persist_mode, unsigned long, kstrtoul)
122699ea521SShiju Jose MR_ATTR_STORE(hpa, set_hpa, u64, kstrtou64)
123699ea521SShiju Jose MR_ATTR_STORE(dpa, set_dpa, u64, kstrtou64)
124699ea521SShiju Jose MR_ATTR_STORE(nibble_mask, set_nibble_mask, unsigned long, kstrtoul)
12581e42fc1SShiju Jose MR_ATTR_STORE(bank_group, set_bank_group, unsigned long, kstrtoul)
12681e42fc1SShiju Jose MR_ATTR_STORE(bank, set_bank, unsigned long, kstrtoul)
12781e42fc1SShiju Jose MR_ATTR_STORE(rank, set_rank, unsigned long, kstrtoul)
12881e42fc1SShiju Jose MR_ATTR_STORE(row, set_row, unsigned long, kstrtoul)
12981e42fc1SShiju Jose MR_ATTR_STORE(column, set_column, unsigned long, kstrtoul)
13081e42fc1SShiju Jose MR_ATTR_STORE(channel, set_channel, unsigned long, kstrtoul)
13181e42fc1SShiju Jose MR_ATTR_STORE(sub_channel, set_sub_channel, unsigned long, kstrtoul)
132699ea521SShiju Jose
133699ea521SShiju Jose #define MR_DO_OP(attrib, cb) \
134699ea521SShiju Jose static ssize_t attrib##_store(struct device *ras_feat_dev, \
135699ea521SShiju Jose struct device_attribute *attr, \
136699ea521SShiju Jose const char *buf, size_t len) \
137699ea521SShiju Jose { \
138699ea521SShiju Jose u8 inst = TO_MR_DEV_ATTR(attr)->instance; \
139699ea521SShiju Jose struct edac_dev_feat_ctx *ctx = dev_get_drvdata(ras_feat_dev); \
140699ea521SShiju Jose const struct edac_mem_repair_ops *ops = ctx->mem_repair[inst].mem_repair_ops; \
141699ea521SShiju Jose unsigned long data; \
142699ea521SShiju Jose int ret; \
143699ea521SShiju Jose \
144699ea521SShiju Jose ret = kstrtoul(buf, 0, &data); \
145699ea521SShiju Jose if (ret < 0) \
146699ea521SShiju Jose return ret; \
147699ea521SShiju Jose \
148699ea521SShiju Jose ret = ops->cb(ras_feat_dev->parent, ctx->mem_repair[inst].private, data); \
149699ea521SShiju Jose if (ret) \
150699ea521SShiju Jose return ret; \
151699ea521SShiju Jose \
152699ea521SShiju Jose return len; \
153699ea521SShiju Jose }
154699ea521SShiju Jose
155699ea521SShiju Jose MR_DO_OP(repair, do_repair)
156699ea521SShiju Jose
157699ea521SShiju Jose static umode_t mem_repair_attr_visible(struct kobject *kobj, struct attribute *a, int attr_id)
158699ea521SShiju Jose {
159699ea521SShiju Jose struct device *ras_feat_dev = kobj_to_dev(kobj);
160699ea521SShiju Jose struct device_attribute *dev_attr = container_of(a, struct device_attribute, attr);
161699ea521SShiju Jose struct edac_dev_feat_ctx *ctx = dev_get_drvdata(ras_feat_dev);
162699ea521SShiju Jose u8 inst = TO_MR_DEV_ATTR(dev_attr)->instance;
163699ea521SShiju Jose const struct edac_mem_repair_ops *ops = ctx->mem_repair[inst].mem_repair_ops;
164699ea521SShiju Jose
165699ea521SShiju Jose switch (attr_id) {
166699ea521SShiju Jose case MR_TYPE:
167699ea521SShiju Jose if (ops->get_repair_type)
168699ea521SShiju Jose return a->mode;
169699ea521SShiju Jose break;
170699ea521SShiju Jose case MR_PERSIST_MODE:
171699ea521SShiju Jose if (ops->get_persist_mode) {
172699ea521SShiju Jose if (ops->set_persist_mode)
173699ea521SShiju Jose return a->mode;
174699ea521SShiju Jose else
175699ea521SShiju Jose return 0444;
176699ea521SShiju Jose }
177699ea521SShiju Jose break;
178699ea521SShiju Jose case MR_SAFE_IN_USE:
179699ea521SShiju Jose if (ops->get_repair_safe_when_in_use)
180699ea521SShiju Jose return a->mode;
181699ea521SShiju Jose break;
182699ea521SShiju Jose case MR_HPA:
183699ea521SShiju Jose if (ops->get_hpa) {
184699ea521SShiju Jose if (ops->set_hpa)
185699ea521SShiju Jose return a->mode;
186699ea521SShiju Jose else
187699ea521SShiju Jose return 0444;
188699ea521SShiju Jose }
189699ea521SShiju Jose break;
190699ea521SShiju Jose case MR_MIN_HPA:
191699ea521SShiju Jose if (ops->get_min_hpa)
192699ea521SShiju Jose return a->mode;
193699ea521SShiju Jose break;
194699ea521SShiju Jose case MR_MAX_HPA:
195699ea521SShiju Jose if (ops->get_max_hpa)
196699ea521SShiju Jose return a->mode;
197699ea521SShiju Jose break;
198699ea521SShiju Jose case MR_DPA:
199699ea521SShiju Jose if (ops->get_dpa) {
200699ea521SShiju Jose if (ops->set_dpa)
201699ea521SShiju Jose return a->mode;
202699ea521SShiju Jose else
203699ea521SShiju Jose return 0444;
204699ea521SShiju Jose }
205699ea521SShiju Jose break;
206699ea521SShiju Jose case MR_MIN_DPA:
207699ea521SShiju Jose if (ops->get_min_dpa)
208699ea521SShiju Jose return a->mode;
209699ea521SShiju Jose break;
210699ea521SShiju Jose case MR_MAX_DPA:
211699ea521SShiju Jose if (ops->get_max_dpa)
212699ea521SShiju Jose return a->mode;
213699ea521SShiju Jose break;
214699ea521SShiju Jose case MR_NIBBLE_MASK:
215699ea521SShiju Jose if (ops->get_nibble_mask) {
216699ea521SShiju Jose if (ops->set_nibble_mask)
217699ea521SShiju Jose return a->mode;
218699ea521SShiju Jose else
219699ea521SShiju Jose return 0444;
220699ea521SShiju Jose }
221699ea521SShiju Jose break;
22281e42fc1SShiju Jose case MR_BANK_GROUP:
22381e42fc1SShiju Jose if (ops->get_bank_group) {
22481e42fc1SShiju Jose if (ops->set_bank_group)
22581e42fc1SShiju Jose return a->mode;
22681e42fc1SShiju Jose else
22781e42fc1SShiju Jose return 0444;
22881e42fc1SShiju Jose }
22981e42fc1SShiju Jose break;
23081e42fc1SShiju Jose case MR_BANK:
23181e42fc1SShiju Jose if (ops->get_bank) {
23281e42fc1SShiju Jose if (ops->set_bank)
23381e42fc1SShiju Jose return a->mode;
23481e42fc1SShiju Jose else
23581e42fc1SShiju Jose return 0444;
23681e42fc1SShiju Jose }
23781e42fc1SShiju Jose break;
23881e42fc1SShiju Jose case MR_RANK:
23981e42fc1SShiju Jose if (ops->get_rank) {
24081e42fc1SShiju Jose if (ops->set_rank)
24181e42fc1SShiju Jose return a->mode;
24281e42fc1SShiju Jose else
24381e42fc1SShiju Jose return 0444;
24481e42fc1SShiju Jose }
24581e42fc1SShiju Jose break;
24681e42fc1SShiju Jose case MR_ROW:
24781e42fc1SShiju Jose if (ops->get_row) {
24881e42fc1SShiju Jose if (ops->set_row)
24981e42fc1SShiju Jose return a->mode;
25081e42fc1SShiju Jose else
25181e42fc1SShiju Jose return 0444;
25281e42fc1SShiju Jose }
25381e42fc1SShiju Jose break;
25481e42fc1SShiju Jose case MR_COLUMN:
25581e42fc1SShiju Jose if (ops->get_column) {
25681e42fc1SShiju Jose if (ops->set_column)
25781e42fc1SShiju Jose return a->mode;
25881e42fc1SShiju Jose else
25981e42fc1SShiju Jose return 0444;
26081e42fc1SShiju Jose }
26181e42fc1SShiju Jose break;
26281e42fc1SShiju Jose case MR_CHANNEL:
26381e42fc1SShiju Jose if (ops->get_channel) {
26481e42fc1SShiju Jose if (ops->set_channel)
26581e42fc1SShiju Jose return a->mode;
26681e42fc1SShiju Jose else
26781e42fc1SShiju Jose return 0444;
26881e42fc1SShiju Jose }
26981e42fc1SShiju Jose break;
27081e42fc1SShiju Jose case MR_SUB_CHANNEL:
27181e42fc1SShiju Jose if (ops->get_sub_channel) {
27281e42fc1SShiju Jose if (ops->set_sub_channel)
27381e42fc1SShiju Jose return a->mode;
27481e42fc1SShiju Jose else
27581e42fc1SShiju Jose return 0444;
27681e42fc1SShiju Jose }
27781e42fc1SShiju Jose break;
278699ea521SShiju Jose case MEM_DO_REPAIR:
279699ea521SShiju Jose if (ops->do_repair)
280699ea521SShiju Jose return a->mode;
281699ea521SShiju Jose break;
282699ea521SShiju Jose default:
283699ea521SShiju Jose break;
284699ea521SShiju Jose }
285699ea521SShiju Jose
286699ea521SShiju Jose return 0;
287699ea521SShiju Jose }
288699ea521SShiju Jose
289699ea521SShiju Jose #define MR_ATTR_RO(_name, _instance) \
290699ea521SShiju Jose ((struct edac_mem_repair_dev_attr) { .dev_attr = __ATTR_RO(_name), \
291699ea521SShiju Jose .instance = _instance })
292699ea521SShiju Jose
293699ea521SShiju Jose #define MR_ATTR_WO(_name, _instance) \
294699ea521SShiju Jose ((struct edac_mem_repair_dev_attr) { .dev_attr = __ATTR_WO(_name), \
295699ea521SShiju Jose .instance = _instance })
296699ea521SShiju Jose
297699ea521SShiju Jose #define MR_ATTR_RW(_name, _instance) \
298699ea521SShiju Jose ((struct edac_mem_repair_dev_attr) { .dev_attr = __ATTR_RW(_name), \
299699ea521SShiju Jose .instance = _instance })
300699ea521SShiju Jose
mem_repair_create_desc(struct device * dev,const struct attribute_group ** attr_groups,u8 instance)301699ea521SShiju Jose static int mem_repair_create_desc(struct device *dev,
302699ea521SShiju Jose const struct attribute_group **attr_groups,
303699ea521SShiju Jose u8 instance)
304699ea521SShiju Jose {
305699ea521SShiju Jose struct edac_mem_repair_context *ctx;
306699ea521SShiju Jose struct attribute_group *group;
307699ea521SShiju Jose int i;
308699ea521SShiju Jose struct edac_mem_repair_dev_attr dev_attr[] = {
309699ea521SShiju Jose [MR_TYPE] = MR_ATTR_RO(repair_type, instance),
310699ea521SShiju Jose [MR_PERSIST_MODE] = MR_ATTR_RW(persist_mode, instance),
311699ea521SShiju Jose [MR_SAFE_IN_USE] = MR_ATTR_RO(repair_safe_when_in_use, instance),
312699ea521SShiju Jose [MR_HPA] = MR_ATTR_RW(hpa, instance),
313699ea521SShiju Jose [MR_MIN_HPA] = MR_ATTR_RO(min_hpa, instance),
314699ea521SShiju Jose [MR_MAX_HPA] = MR_ATTR_RO(max_hpa, instance),
315699ea521SShiju Jose [MR_DPA] = MR_ATTR_RW(dpa, instance),
316699ea521SShiju Jose [MR_MIN_DPA] = MR_ATTR_RO(min_dpa, instance),
317699ea521SShiju Jose [MR_MAX_DPA] = MR_ATTR_RO(max_dpa, instance),
318699ea521SShiju Jose [MR_NIBBLE_MASK] = MR_ATTR_RW(nibble_mask, instance),
31981e42fc1SShiju Jose [MR_BANK_GROUP] = MR_ATTR_RW(bank_group, instance),
32081e42fc1SShiju Jose [MR_BANK] = MR_ATTR_RW(bank, instance),
32181e42fc1SShiju Jose [MR_RANK] = MR_ATTR_RW(rank, instance),
32281e42fc1SShiju Jose [MR_ROW] = MR_ATTR_RW(row, instance),
32381e42fc1SShiju Jose [MR_COLUMN] = MR_ATTR_RW(column, instance),
32481e42fc1SShiju Jose [MR_CHANNEL] = MR_ATTR_RW(channel, instance),
32581e42fc1SShiju Jose [MR_SUB_CHANNEL] = MR_ATTR_RW(sub_channel, instance),
326699ea521SShiju Jose [MEM_DO_REPAIR] = MR_ATTR_WO(repair, instance)
327699ea521SShiju Jose };
328699ea521SShiju Jose
329699ea521SShiju Jose ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
330699ea521SShiju Jose if (!ctx)
331699ea521SShiju Jose return -ENOMEM;
332699ea521SShiju Jose
333699ea521SShiju Jose for (i = 0; i < MR_MAX_ATTRS; i++) {
334699ea521SShiju Jose memcpy(&ctx->mem_repair_dev_attr[i],
335699ea521SShiju Jose &dev_attr[i], sizeof(dev_attr[i]));
336*1e14ea90SShiju Jose sysfs_attr_init(&ctx->mem_repair_dev_attr[i].dev_attr.attr);
337699ea521SShiju Jose ctx->mem_repair_attrs[i] =
338699ea521SShiju Jose &ctx->mem_repair_dev_attr[i].dev_attr.attr;
339699ea521SShiju Jose }
340699ea521SShiju Jose
341699ea521SShiju Jose sprintf(ctx->name, "%s%d", "mem_repair", instance);
342699ea521SShiju Jose group = &ctx->group;
343699ea521SShiju Jose group->name = ctx->name;
344699ea521SShiju Jose group->attrs = ctx->mem_repair_attrs;
345699ea521SShiju Jose group->is_visible = mem_repair_attr_visible;
346699ea521SShiju Jose attr_groups[0] = group;
347699ea521SShiju Jose
348699ea521SShiju Jose return 0;
349699ea521SShiju Jose }
350699ea521SShiju Jose
351699ea521SShiju Jose /**
352699ea521SShiju Jose * edac_mem_repair_get_desc - get EDAC memory repair descriptors
353699ea521SShiju Jose * @dev: client device with memory repair feature
354699ea521SShiju Jose * @attr_groups: pointer to attribute group container
355699ea521SShiju Jose * @instance: device's memory repair instance number.
356699ea521SShiju Jose *
357699ea521SShiju Jose * Return:
358699ea521SShiju Jose * * %0 - Success.
359699ea521SShiju Jose * * %-EINVAL - Invalid parameters passed.
360699ea521SShiju Jose * * %-ENOMEM - Dynamic memory allocation failed.
361699ea521SShiju Jose */
edac_mem_repair_get_desc(struct device * dev,const struct attribute_group ** attr_groups,u8 instance)362699ea521SShiju Jose int edac_mem_repair_get_desc(struct device *dev,
363699ea521SShiju Jose const struct attribute_group **attr_groups, u8 instance)
364699ea521SShiju Jose {
365699ea521SShiju Jose if (!dev || !attr_groups)
366699ea521SShiju Jose return -EINVAL;
367699ea521SShiju Jose
368699ea521SShiju Jose return mem_repair_create_desc(dev, attr_groups, instance);
369699ea521SShiju Jose }
370