|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
| 2 | +/* Copyright(c) 2024-2025 Intel Corporation. All rights reserved. */ |
| 3 | +#include <linux/device.h> |
| 4 | +#include <cxl/mailbox.h> |
| 5 | +#include <cxl/features.h> |
| 6 | +#include "cxl.h" |
| 7 | +#include "core.h" |
| 8 | +#include "cxlmem.h" |
| 9 | + |
| 10 | +/* All the features below are exclusive to the kernel */ |
| 11 | +static const uuid_t cxl_exclusive_feats[] = { |
| 12 | + CXL_FEAT_PATROL_SCRUB_UUID, |
| 13 | + CXL_FEAT_ECS_UUID, |
| 14 | + CXL_FEAT_SPPR_UUID, |
| 15 | + CXL_FEAT_HPPR_UUID, |
| 16 | + CXL_FEAT_CACHELINE_SPARING_UUID, |
| 17 | + CXL_FEAT_ROW_SPARING_UUID, |
| 18 | + CXL_FEAT_BANK_SPARING_UUID, |
| 19 | + CXL_FEAT_RANK_SPARING_UUID, |
| 20 | +}; |
| 21 | + |
| 22 | +static bool is_cxl_feature_exclusive(struct cxl_feat_entry *entry) |
| 23 | +{ |
| 24 | + for (int i = 0; i < ARRAY_SIZE(cxl_exclusive_feats); i++) { |
| 25 | + if (uuid_equal(&entry->uuid, &cxl_exclusive_feats[i])) |
| 26 | + return true; |
| 27 | + } |
| 28 | + |
| 29 | + return false; |
| 30 | +} |
| 31 | + |
| 32 | +inline struct cxl_features_state *to_cxlfs(struct cxl_dev_state *cxlds) |
| 33 | +{ |
| 34 | + return cxlds->cxlfs; |
| 35 | +} |
| 36 | +EXPORT_SYMBOL_NS_GPL(to_cxlfs, "CXL"); |
| 37 | + |
| 38 | +static int cxl_get_supported_features_count(struct cxl_mailbox *cxl_mbox) |
| 39 | +{ |
| 40 | + struct cxl_mbox_get_sup_feats_out mbox_out; |
| 41 | + struct cxl_mbox_get_sup_feats_in mbox_in; |
| 42 | + struct cxl_mbox_cmd mbox_cmd; |
| 43 | + int rc; |
| 44 | + |
| 45 | + memset(&mbox_in, 0, sizeof(mbox_in)); |
| 46 | + mbox_in.count = cpu_to_le32(sizeof(mbox_out)); |
| 47 | + memset(&mbox_out, 0, sizeof(mbox_out)); |
| 48 | + mbox_cmd = (struct cxl_mbox_cmd) { |
| 49 | + .opcode = CXL_MBOX_OP_GET_SUPPORTED_FEATURES, |
| 50 | + .size_in = sizeof(mbox_in), |
| 51 | + .payload_in = &mbox_in, |
| 52 | + .size_out = sizeof(mbox_out), |
| 53 | + .payload_out = &mbox_out, |
| 54 | + .min_out = sizeof(mbox_out), |
| 55 | + }; |
| 56 | + rc = cxl_internal_send_cmd(cxl_mbox, &mbox_cmd); |
| 57 | + if (rc < 0) |
| 58 | + return rc; |
| 59 | + |
| 60 | + return le16_to_cpu(mbox_out.supported_feats); |
| 61 | +} |
| 62 | + |
| 63 | +static struct cxl_feat_entries * |
| 64 | +get_supported_features(struct cxl_features_state *cxlfs) |
| 65 | +{ |
| 66 | + int remain_feats, max_size, max_feats, start, rc, hdr_size; |
| 67 | + struct cxl_mailbox *cxl_mbox = &cxlfs->cxlds->cxl_mbox; |
| 68 | + int feat_size = sizeof(struct cxl_feat_entry); |
| 69 | + struct cxl_mbox_get_sup_feats_in mbox_in; |
| 70 | + struct cxl_feat_entry *entry; |
| 71 | + struct cxl_mbox_cmd mbox_cmd; |
| 72 | + int user_feats = 0; |
| 73 | + int count; |
| 74 | + |
| 75 | + count = cxl_get_supported_features_count(cxl_mbox); |
| 76 | + if (count <= 0) |
| 77 | + return NULL; |
| 78 | + |
| 79 | + struct cxl_feat_entries *entries __free(kvfree) = |
| 80 | + kvmalloc(struct_size(entries, ent, count), GFP_KERNEL); |
| 81 | + if (!entries) |
| 82 | + return NULL; |
| 83 | + |
| 84 | + struct cxl_mbox_get_sup_feats_out *mbox_out __free(kvfree) = |
| 85 | + kvmalloc(cxl_mbox->payload_size, GFP_KERNEL); |
| 86 | + if (!mbox_out) |
| 87 | + return NULL; |
| 88 | + |
| 89 | + hdr_size = struct_size(mbox_out, ents, 0); |
| 90 | + max_size = cxl_mbox->payload_size - hdr_size; |
| 91 | + /* max feat entries that can fit in mailbox max payload size */ |
| 92 | + max_feats = max_size / feat_size; |
| 93 | + entry = entries->ent; |
| 94 | + |
| 95 | + start = 0; |
| 96 | + remain_feats = count; |
| 97 | + do { |
| 98 | + int retrieved, alloc_size, copy_feats; |
| 99 | + int num_entries; |
| 100 | + |
| 101 | + if (remain_feats > max_feats) { |
| 102 | + alloc_size = struct_size(mbox_out, ents, max_feats); |
| 103 | + remain_feats = remain_feats - max_feats; |
| 104 | + copy_feats = max_feats; |
| 105 | + } else { |
| 106 | + alloc_size = struct_size(mbox_out, ents, remain_feats); |
| 107 | + copy_feats = remain_feats; |
| 108 | + remain_feats = 0; |
| 109 | + } |
| 110 | + |
| 111 | + memset(&mbox_in, 0, sizeof(mbox_in)); |
| 112 | + mbox_in.count = cpu_to_le32(alloc_size); |
| 113 | + mbox_in.start_idx = cpu_to_le16(start); |
| 114 | + memset(mbox_out, 0, alloc_size); |
| 115 | + mbox_cmd = (struct cxl_mbox_cmd) { |
| 116 | + .opcode = CXL_MBOX_OP_GET_SUPPORTED_FEATURES, |
| 117 | + .size_in = sizeof(mbox_in), |
| 118 | + .payload_in = &mbox_in, |
| 119 | + .size_out = alloc_size, |
| 120 | + .payload_out = mbox_out, |
| 121 | + .min_out = hdr_size, |
| 122 | + }; |
| 123 | + rc = cxl_internal_send_cmd(cxl_mbox, &mbox_cmd); |
| 124 | + if (rc < 0) |
| 125 | + return NULL; |
| 126 | + |
| 127 | + if (mbox_cmd.size_out <= hdr_size) |
| 128 | + return NULL; |
| 129 | + |
| 130 | + /* |
| 131 | + * Make sure retrieved out buffer is multiple of feature |
| 132 | + * entries. |
| 133 | + */ |
| 134 | + retrieved = mbox_cmd.size_out - hdr_size; |
| 135 | + if (retrieved % feat_size) |
| 136 | + return NULL; |
| 137 | + |
| 138 | + num_entries = le16_to_cpu(mbox_out->num_entries); |
| 139 | + /* |
| 140 | + * If the reported output entries * defined entry size != |
| 141 | + * retrieved output bytes, then the output package is incorrect. |
| 142 | + */ |
| 143 | + if (num_entries * feat_size != retrieved) |
| 144 | + return NULL; |
| 145 | + |
| 146 | + memcpy(entry, mbox_out->ents, retrieved); |
| 147 | + for (int i = 0; i < num_entries; i++) { |
| 148 | + if (!is_cxl_feature_exclusive(entry + i)) |
| 149 | + user_feats++; |
| 150 | + } |
| 151 | + entry += num_entries; |
| 152 | + /* |
| 153 | + * If the number of output entries is less than expected, add the |
| 154 | + * remaining entries to the next batch. |
| 155 | + */ |
| 156 | + remain_feats += copy_feats - num_entries; |
| 157 | + start += num_entries; |
| 158 | + } while (remain_feats); |
| 159 | + |
| 160 | + entries->num_features = count; |
| 161 | + entries->num_user_features = user_feats; |
| 162 | + |
| 163 | + return no_free_ptr(entries); |
| 164 | +} |
| 165 | + |
| 166 | +static void free_cxlfs(void *_cxlfs) |
| 167 | +{ |
| 168 | + struct cxl_features_state *cxlfs = _cxlfs; |
| 169 | + struct cxl_dev_state *cxlds = cxlfs->cxlds; |
| 170 | + |
| 171 | + cxlds->cxlfs = NULL; |
| 172 | + kvfree(cxlfs->entries); |
| 173 | + kfree(cxlfs); |
| 174 | +} |
| 175 | + |
| 176 | +/** |
| 177 | + * devm_cxl_setup_features() - Allocate and initialize features context |
| 178 | + * @cxlds: CXL device context |
| 179 | + * |
| 180 | + * Return 0 on success or -errno on failure. |
| 181 | + */ |
| 182 | +int devm_cxl_setup_features(struct cxl_dev_state *cxlds) |
| 183 | +{ |
| 184 | + struct cxl_mailbox *cxl_mbox = &cxlds->cxl_mbox; |
| 185 | + |
| 186 | + if (cxl_mbox->feat_cap < CXL_FEATURES_RO) |
| 187 | + return -ENODEV; |
| 188 | + |
| 189 | + struct cxl_features_state *cxlfs __free(kfree) = |
| 190 | + kzalloc(sizeof(*cxlfs), GFP_KERNEL); |
| 191 | + if (!cxlfs) |
| 192 | + return -ENOMEM; |
| 193 | + |
| 194 | + cxlfs->cxlds = cxlds; |
| 195 | + |
| 196 | + cxlfs->entries = get_supported_features(cxlfs); |
| 197 | + if (!cxlfs->entries) |
| 198 | + return -ENOMEM; |
| 199 | + |
| 200 | + cxlds->cxlfs = cxlfs; |
| 201 | + |
| 202 | + return devm_add_action_or_reset(cxlds->dev, free_cxlfs, no_free_ptr(cxlfs)); |
| 203 | +} |
| 204 | +EXPORT_SYMBOL_NS_GPL(devm_cxl_setup_features, "CXL"); |
| 205 | + |
| 206 | +size_t cxl_get_feature(struct cxl_mailbox *cxl_mbox, const uuid_t *feat_uuid, |
| 207 | + enum cxl_get_feat_selection selection, |
| 208 | + void *feat_out, size_t feat_out_size, u16 offset, |
| 209 | + u16 *return_code) |
| 210 | +{ |
| 211 | + size_t data_to_rd_size, size_out; |
| 212 | + struct cxl_mbox_get_feat_in pi; |
| 213 | + struct cxl_mbox_cmd mbox_cmd; |
| 214 | + size_t data_rcvd_size = 0; |
| 215 | + int rc; |
| 216 | + |
| 217 | + if (return_code) |
| 218 | + *return_code = CXL_MBOX_CMD_RC_INPUT; |
| 219 | + |
| 220 | + if (!feat_out || !feat_out_size) |
| 221 | + return 0; |
| 222 | + |
| 223 | + size_out = min(feat_out_size, cxl_mbox->payload_size); |
| 224 | + uuid_copy(&pi.uuid, feat_uuid); |
| 225 | + pi.selection = selection; |
| 226 | + do { |
| 227 | + data_to_rd_size = min(feat_out_size - data_rcvd_size, |
| 228 | + cxl_mbox->payload_size); |
| 229 | + pi.offset = cpu_to_le16(offset + data_rcvd_size); |
| 230 | + pi.count = cpu_to_le16(data_to_rd_size); |
| 231 | + |
| 232 | + mbox_cmd = (struct cxl_mbox_cmd) { |
| 233 | + .opcode = CXL_MBOX_OP_GET_FEATURE, |
| 234 | + .size_in = sizeof(pi), |
| 235 | + .payload_in = &pi, |
| 236 | + .size_out = size_out, |
| 237 | + .payload_out = feat_out + data_rcvd_size, |
| 238 | + .min_out = data_to_rd_size, |
| 239 | + }; |
| 240 | + rc = cxl_internal_send_cmd(cxl_mbox, &mbox_cmd); |
| 241 | + if (rc < 0 || !mbox_cmd.size_out) { |
| 242 | + if (return_code) |
| 243 | + *return_code = mbox_cmd.return_code; |
| 244 | + return 0; |
| 245 | + } |
| 246 | + data_rcvd_size += mbox_cmd.size_out; |
| 247 | + } while (data_rcvd_size < feat_out_size); |
| 248 | + |
| 249 | + if (return_code) |
| 250 | + *return_code = CXL_MBOX_CMD_RC_SUCCESS; |
| 251 | + |
| 252 | + return data_rcvd_size; |
| 253 | +} |
| 254 | + |
| 255 | +/* |
| 256 | + * FEAT_DATA_MIN_PAYLOAD_SIZE - min extra number of bytes should be |
| 257 | + * available in the mailbox for storing the actual feature data so that |
| 258 | + * the feature data transfer would work as expected. |
| 259 | + */ |
| 260 | +#define FEAT_DATA_MIN_PAYLOAD_SIZE 10 |
| 261 | +int cxl_set_feature(struct cxl_mailbox *cxl_mbox, |
| 262 | + const uuid_t *feat_uuid, u8 feat_version, |
| 263 | + const void *feat_data, size_t feat_data_size, |
| 264 | + u32 feat_flag, u16 offset, u16 *return_code) |
| 265 | +{ |
| 266 | + size_t data_in_size, data_sent_size = 0; |
| 267 | + struct cxl_mbox_cmd mbox_cmd; |
| 268 | + size_t hdr_size; |
| 269 | + |
| 270 | + if (return_code) |
| 271 | + *return_code = CXL_MBOX_CMD_RC_INPUT; |
| 272 | + |
| 273 | + struct cxl_mbox_set_feat_in *pi __free(kfree) = |
| 274 | + kzalloc(cxl_mbox->payload_size, GFP_KERNEL); |
| 275 | + if (!pi) |
| 276 | + return -ENOMEM; |
| 277 | + |
| 278 | + uuid_copy(&pi->uuid, feat_uuid); |
| 279 | + pi->version = feat_version; |
| 280 | + feat_flag &= ~CXL_SET_FEAT_FLAG_DATA_TRANSFER_MASK; |
| 281 | + feat_flag |= CXL_SET_FEAT_FLAG_DATA_SAVED_ACROSS_RESET; |
| 282 | + hdr_size = sizeof(pi->hdr); |
| 283 | + /* |
| 284 | + * Check minimum mbox payload size is available for |
| 285 | + * the feature data transfer. |
| 286 | + */ |
| 287 | + if (hdr_size + FEAT_DATA_MIN_PAYLOAD_SIZE > cxl_mbox->payload_size) |
| 288 | + return -ENOMEM; |
| 289 | + |
| 290 | + if (hdr_size + feat_data_size <= cxl_mbox->payload_size) { |
| 291 | + pi->flags = cpu_to_le32(feat_flag | |
| 292 | + CXL_SET_FEAT_FLAG_FULL_DATA_TRANSFER); |
| 293 | + data_in_size = feat_data_size; |
| 294 | + } else { |
| 295 | + pi->flags = cpu_to_le32(feat_flag | |
| 296 | + CXL_SET_FEAT_FLAG_INITIATE_DATA_TRANSFER); |
| 297 | + data_in_size = cxl_mbox->payload_size - hdr_size; |
| 298 | + } |
| 299 | + |
| 300 | + do { |
| 301 | + int rc; |
| 302 | + |
| 303 | + pi->offset = cpu_to_le16(offset + data_sent_size); |
| 304 | + memcpy(pi->feat_data, feat_data + data_sent_size, data_in_size); |
| 305 | + mbox_cmd = (struct cxl_mbox_cmd) { |
| 306 | + .opcode = CXL_MBOX_OP_SET_FEATURE, |
| 307 | + .size_in = hdr_size + data_in_size, |
| 308 | + .payload_in = pi, |
| 309 | + }; |
| 310 | + rc = cxl_internal_send_cmd(cxl_mbox, &mbox_cmd); |
| 311 | + if (rc < 0) { |
| 312 | + if (return_code) |
| 313 | + *return_code = mbox_cmd.return_code; |
| 314 | + return rc; |
| 315 | + } |
| 316 | + |
| 317 | + data_sent_size += data_in_size; |
| 318 | + if (data_sent_size >= feat_data_size) { |
| 319 | + if (return_code) |
| 320 | + *return_code = CXL_MBOX_CMD_RC_SUCCESS; |
| 321 | + return 0; |
| 322 | + } |
| 323 | + |
| 324 | + if ((feat_data_size - data_sent_size) <= (cxl_mbox->payload_size - hdr_size)) { |
| 325 | + data_in_size = feat_data_size - data_sent_size; |
| 326 | + pi->flags = cpu_to_le32(feat_flag | |
| 327 | + CXL_SET_FEAT_FLAG_FINISH_DATA_TRANSFER); |
| 328 | + } else { |
| 329 | + pi->flags = cpu_to_le32(feat_flag | |
| 330 | + CXL_SET_FEAT_FLAG_CONTINUE_DATA_TRANSFER); |
| 331 | + } |
| 332 | + } while (true); |
| 333 | +} |
0 commit comments