1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * The generic ECS driver is designed to support control of on-die error
4 * check scrub (e.g., DDR5 ECS). The common sysfs ECS interface abstracts
5 * the control of various ECS functionalities into a unified set of functions.
6 *
7 * Copyright (c) 2024-2025 HiSilicon Limited.
8 */
9
10 #include <linux/edac.h>
11
12 #define EDAC_ECS_FRU_NAME "ecs_fru"
13
14 enum edac_ecs_attributes {
15 ECS_LOG_ENTRY_TYPE,
16 ECS_MODE,
17 ECS_RESET,
18 ECS_THRESHOLD,
19 ECS_MAX_ATTRS
20 };
21
22 struct edac_ecs_dev_attr {
23 struct device_attribute dev_attr;
24 int fru_id;
25 };
26
27 struct edac_ecs_fru_context {
28 char name[EDAC_FEAT_NAME_LEN];
29 struct edac_ecs_dev_attr dev_attr[ECS_MAX_ATTRS];
30 struct attribute *ecs_attrs[ECS_MAX_ATTRS + 1];
31 struct attribute_group group;
32 };
33
34 struct edac_ecs_context {
35 u16 num_media_frus;
36 struct edac_ecs_fru_context *fru_ctxs;
37 };
38
39 #define TO_ECS_DEV_ATTR(_dev_attr) \
40 container_of(_dev_attr, struct edac_ecs_dev_attr, dev_attr)
41
42 #define EDAC_ECS_ATTR_SHOW(attrib, cb, type, format) \
43 static ssize_t attrib##_show(struct device *ras_feat_dev, \
44 struct device_attribute *attr, char *buf) \
45 { \
46 struct edac_ecs_dev_attr *dev_attr = TO_ECS_DEV_ATTR(attr); \
47 struct edac_dev_feat_ctx *ctx = dev_get_drvdata(ras_feat_dev); \
48 const struct edac_ecs_ops *ops = ctx->ecs.ecs_ops; \
49 type data; \
50 int ret; \
51 \
52 ret = ops->cb(ras_feat_dev->parent, ctx->ecs.private, \
53 dev_attr->fru_id, &data); \
54 if (ret) \
55 return ret; \
56 \
57 return sysfs_emit(buf, format, data); \
58 }
59
60 EDAC_ECS_ATTR_SHOW(log_entry_type, get_log_entry_type, u32, "%u\n")
61 EDAC_ECS_ATTR_SHOW(mode, get_mode, u32, "%u\n")
62 EDAC_ECS_ATTR_SHOW(threshold, get_threshold, u32, "%u\n")
63
64 #define EDAC_ECS_ATTR_STORE(attrib, cb, type, conv_func) \
65 static ssize_t attrib##_store(struct device *ras_feat_dev, \
66 struct device_attribute *attr, \
67 const char *buf, size_t len) \
68 { \
69 struct edac_ecs_dev_attr *dev_attr = TO_ECS_DEV_ATTR(attr); \
70 struct edac_dev_feat_ctx *ctx = dev_get_drvdata(ras_feat_dev); \
71 const struct edac_ecs_ops *ops = ctx->ecs.ecs_ops; \
72 type data; \
73 int ret; \
74 \
75 ret = conv_func(buf, 0, &data); \
76 if (ret < 0) \
77 return ret; \
78 \
79 ret = ops->cb(ras_feat_dev->parent, ctx->ecs.private, \
80 dev_attr->fru_id, data); \
81 if (ret) \
82 return ret; \
83 \
84 return len; \
85 }
86
EDAC_ECS_ATTR_STORE(log_entry_type,set_log_entry_type,unsigned long,kstrtoul)87 EDAC_ECS_ATTR_STORE(log_entry_type, set_log_entry_type, unsigned long, kstrtoul)
88 EDAC_ECS_ATTR_STORE(mode, set_mode, unsigned long, kstrtoul)
89 EDAC_ECS_ATTR_STORE(reset, reset, unsigned long, kstrtoul)
90 EDAC_ECS_ATTR_STORE(threshold, set_threshold, unsigned long, kstrtoul)
91
92 static umode_t ecs_attr_visible(struct kobject *kobj, struct attribute *a, int attr_id)
93 {
94 struct device *ras_feat_dev = kobj_to_dev(kobj);
95 struct edac_dev_feat_ctx *ctx = dev_get_drvdata(ras_feat_dev);
96 const struct edac_ecs_ops *ops = ctx->ecs.ecs_ops;
97
98 switch (attr_id) {
99 case ECS_LOG_ENTRY_TYPE:
100 if (ops->get_log_entry_type) {
101 if (ops->set_log_entry_type)
102 return a->mode;
103 else
104 return 0444;
105 }
106 break;
107 case ECS_MODE:
108 if (ops->get_mode) {
109 if (ops->set_mode)
110 return a->mode;
111 else
112 return 0444;
113 }
114 break;
115 case ECS_RESET:
116 if (ops->reset)
117 return a->mode;
118 break;
119 case ECS_THRESHOLD:
120 if (ops->get_threshold) {
121 if (ops->set_threshold)
122 return a->mode;
123 else
124 return 0444;
125 }
126 break;
127 default:
128 break;
129 }
130
131 return 0;
132 }
133
134 #define EDAC_ECS_ATTR_RO(_name, _fru_id) \
135 ((struct edac_ecs_dev_attr) { .dev_attr = __ATTR_RO(_name), \
136 .fru_id = _fru_id })
137
138 #define EDAC_ECS_ATTR_WO(_name, _fru_id) \
139 ((struct edac_ecs_dev_attr) { .dev_attr = __ATTR_WO(_name), \
140 .fru_id = _fru_id })
141
142 #define EDAC_ECS_ATTR_RW(_name, _fru_id) \
143 ((struct edac_ecs_dev_attr) { .dev_attr = __ATTR_RW(_name), \
144 .fru_id = _fru_id })
145
ecs_create_desc(struct device * ecs_dev,const struct attribute_group ** attr_groups,u16 num_media_frus)146 static int ecs_create_desc(struct device *ecs_dev, const struct attribute_group **attr_groups,
147 u16 num_media_frus)
148 {
149 struct edac_ecs_context *ecs_ctx;
150 u32 fru;
151
152 ecs_ctx = devm_kzalloc(ecs_dev, sizeof(*ecs_ctx), GFP_KERNEL);
153 if (!ecs_ctx)
154 return -ENOMEM;
155
156 ecs_ctx->num_media_frus = num_media_frus;
157 ecs_ctx->fru_ctxs = devm_kcalloc(ecs_dev, num_media_frus,
158 sizeof(*ecs_ctx->fru_ctxs),
159 GFP_KERNEL);
160 if (!ecs_ctx->fru_ctxs)
161 return -ENOMEM;
162
163 for (fru = 0; fru < num_media_frus; fru++) {
164 struct edac_ecs_fru_context *fru_ctx = &ecs_ctx->fru_ctxs[fru];
165 struct attribute_group *group = &fru_ctx->group;
166 int i;
167
168 fru_ctx->dev_attr[ECS_LOG_ENTRY_TYPE] = EDAC_ECS_ATTR_RW(log_entry_type, fru);
169 fru_ctx->dev_attr[ECS_MODE] = EDAC_ECS_ATTR_RW(mode, fru);
170 fru_ctx->dev_attr[ECS_RESET] = EDAC_ECS_ATTR_WO(reset, fru);
171 fru_ctx->dev_attr[ECS_THRESHOLD] = EDAC_ECS_ATTR_RW(threshold, fru);
172
173 for (i = 0; i < ECS_MAX_ATTRS; i++) {
174 sysfs_attr_init(&fru_ctx->dev_attr[i].dev_attr.attr);
175 fru_ctx->ecs_attrs[i] = &fru_ctx->dev_attr[i].dev_attr.attr;
176 }
177
178 sprintf(fru_ctx->name, "%s%d", EDAC_ECS_FRU_NAME, fru);
179 group->name = fru_ctx->name;
180 group->attrs = fru_ctx->ecs_attrs;
181 group->is_visible = ecs_attr_visible;
182
183 attr_groups[fru] = group;
184 }
185
186 return 0;
187 }
188
189 /**
190 * edac_ecs_get_desc - get EDAC ECS descriptors
191 * @ecs_dev: client device, supports ECS feature
192 * @attr_groups: pointer to attribute group container
193 * @num_media_frus: number of media FRUs in the device
194 *
195 * Return:
196 * * %0 - Success.
197 * * %-EINVAL - Invalid parameters passed.
198 * * %-ENOMEM - Dynamic memory allocation failed.
199 */
edac_ecs_get_desc(struct device * ecs_dev,const struct attribute_group ** attr_groups,u16 num_media_frus)200 int edac_ecs_get_desc(struct device *ecs_dev,
201 const struct attribute_group **attr_groups, u16 num_media_frus)
202 {
203 if (!ecs_dev || !attr_groups || !num_media_frus)
204 return -EINVAL;
205
206 return ecs_create_desc(ecs_dev, attr_groups, num_media_frus);
207 }
208