1 // SPDX-License-Identifier: GPL-2.0-only 2 // Copyright(c) 2021 Intel Corporation. All rights reserved. 3 4 #include <linux/platform_device.h> 5 #include <linux/mod_devicetable.h> 6 #include <linux/module.h> 7 #include <linux/delay.h> 8 #include <linux/sizes.h> 9 #include <linux/bits.h> 10 #include <cxlmem.h> 11 12 #define LSA_SIZE SZ_128K 13 #define EFFECT(x) (1U << x) 14 15 static struct cxl_cel_entry mock_cel[] = { 16 { 17 .opcode = cpu_to_le16(CXL_MBOX_OP_GET_SUPPORTED_LOGS), 18 .effect = cpu_to_le16(0), 19 }, 20 { 21 .opcode = cpu_to_le16(CXL_MBOX_OP_IDENTIFY), 22 .effect = cpu_to_le16(0), 23 }, 24 { 25 .opcode = cpu_to_le16(CXL_MBOX_OP_GET_LSA), 26 .effect = cpu_to_le16(0), 27 }, 28 { 29 .opcode = cpu_to_le16(CXL_MBOX_OP_SET_LSA), 30 .effect = cpu_to_le16(EFFECT(1) | EFFECT(2)), 31 }, 32 { 33 .opcode = cpu_to_le16(CXL_MBOX_OP_GET_HEALTH_INFO), 34 .effect = cpu_to_le16(0), 35 }, 36 }; 37 38 /* See CXL 2.0 Table 181 Get Health Info Output Payload */ 39 struct cxl_mbox_health_info { 40 u8 health_status; 41 u8 media_status; 42 u8 ext_status; 43 u8 life_used; 44 __le16 temperature; 45 __le32 dirty_shutdowns; 46 __le32 volatile_errors; 47 __le32 pmem_errors; 48 } __packed; 49 50 static struct { 51 struct cxl_mbox_get_supported_logs gsl; 52 struct cxl_gsl_entry entry; 53 } mock_gsl_payload = { 54 .gsl = { 55 .entries = cpu_to_le16(1), 56 }, 57 .entry = { 58 .uuid = DEFINE_CXL_CEL_UUID, 59 .size = cpu_to_le32(sizeof(mock_cel)), 60 }, 61 }; 62 63 static int mock_gsl(struct cxl_mbox_cmd *cmd) 64 { 65 if (cmd->size_out < sizeof(mock_gsl_payload)) 66 return -EINVAL; 67 68 memcpy(cmd->payload_out, &mock_gsl_payload, sizeof(mock_gsl_payload)); 69 cmd->size_out = sizeof(mock_gsl_payload); 70 71 return 0; 72 } 73 74 static int mock_get_log(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd) 75 { 76 struct cxl_mbox_get_log *gl = cmd->payload_in; 77 u32 offset = le32_to_cpu(gl->offset); 78 u32 length = le32_to_cpu(gl->length); 79 uuid_t uuid = DEFINE_CXL_CEL_UUID; 80 void *data = &mock_cel; 81 82 if (cmd->size_in < sizeof(*gl)) 83 return -EINVAL; 84 if (length > cxlds->payload_size) 85 return -EINVAL; 86 if (offset + length > sizeof(mock_cel)) 87 return -EINVAL; 88 if (!uuid_equal(&gl->uuid, &uuid)) 89 return -EINVAL; 90 if (length > cmd->size_out) 91 return -EINVAL; 92 93 memcpy(cmd->payload_out, data + offset, length); 94 95 return 0; 96 } 97 98 static int mock_id(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd) 99 { 100 struct platform_device *pdev = to_platform_device(cxlds->dev); 101 struct cxl_mbox_identify id = { 102 .fw_revision = { "mock fw v1 " }, 103 .lsa_size = cpu_to_le32(LSA_SIZE), 104 /* FIXME: Add partition support */ 105 .partition_align = cpu_to_le64(0), 106 }; 107 u64 capacity = 0; 108 int i; 109 110 if (cmd->size_out < sizeof(id)) 111 return -EINVAL; 112 113 for (i = 0; i < 2; i++) { 114 struct resource *res; 115 116 res = platform_get_resource(pdev, IORESOURCE_MEM, i); 117 if (!res) 118 break; 119 120 capacity += resource_size(res) / CXL_CAPACITY_MULTIPLIER; 121 122 if (le64_to_cpu(id.partition_align)) 123 continue; 124 125 if (res->desc == IORES_DESC_PERSISTENT_MEMORY) 126 id.persistent_capacity = cpu_to_le64( 127 resource_size(res) / CXL_CAPACITY_MULTIPLIER); 128 else 129 id.volatile_capacity = cpu_to_le64( 130 resource_size(res) / CXL_CAPACITY_MULTIPLIER); 131 } 132 133 id.total_capacity = cpu_to_le64(capacity); 134 135 memcpy(cmd->payload_out, &id, sizeof(id)); 136 137 return 0; 138 } 139 140 static int mock_get_lsa(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd) 141 { 142 struct cxl_mbox_get_lsa *get_lsa = cmd->payload_in; 143 void *lsa = dev_get_drvdata(cxlds->dev); 144 u32 offset, length; 145 146 if (sizeof(*get_lsa) > cmd->size_in) 147 return -EINVAL; 148 offset = le32_to_cpu(get_lsa->offset); 149 length = le32_to_cpu(get_lsa->length); 150 if (offset + length > LSA_SIZE) 151 return -EINVAL; 152 if (length > cmd->size_out) 153 return -EINVAL; 154 155 memcpy(cmd->payload_out, lsa + offset, length); 156 return 0; 157 } 158 159 static int mock_set_lsa(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd) 160 { 161 struct cxl_mbox_set_lsa *set_lsa = cmd->payload_in; 162 void *lsa = dev_get_drvdata(cxlds->dev); 163 u32 offset, length; 164 165 if (sizeof(*set_lsa) > cmd->size_in) 166 return -EINVAL; 167 offset = le32_to_cpu(set_lsa->offset); 168 length = cmd->size_in - sizeof(*set_lsa); 169 if (offset + length > LSA_SIZE) 170 return -EINVAL; 171 172 memcpy(lsa + offset, &set_lsa->data[0], length); 173 return 0; 174 } 175 176 static int mock_health_info(struct cxl_dev_state *cxlds, 177 struct cxl_mbox_cmd *cmd) 178 { 179 struct cxl_mbox_health_info health_info = { 180 /* set flags for maint needed, perf degraded, hw replacement */ 181 .health_status = 0x7, 182 /* set media status to "All Data Lost" */ 183 .media_status = 0x3, 184 /* 185 * set ext_status flags for: 186 * ext_life_used: normal, 187 * ext_temperature: critical, 188 * ext_corrected_volatile: warning, 189 * ext_corrected_persistent: normal, 190 */ 191 .ext_status = 0x18, 192 .life_used = 15, 193 .temperature = cpu_to_le16(25), 194 .dirty_shutdowns = cpu_to_le32(10), 195 .volatile_errors = cpu_to_le32(20), 196 .pmem_errors = cpu_to_le32(30), 197 }; 198 199 if (cmd->size_out < sizeof(health_info)) 200 return -EINVAL; 201 202 memcpy(cmd->payload_out, &health_info, sizeof(health_info)); 203 return 0; 204 } 205 206 static int cxl_mock_mbox_send(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd) 207 { 208 struct device *dev = cxlds->dev; 209 int rc = -EIO; 210 211 switch (cmd->opcode) { 212 case CXL_MBOX_OP_GET_SUPPORTED_LOGS: 213 rc = mock_gsl(cmd); 214 break; 215 case CXL_MBOX_OP_GET_LOG: 216 rc = mock_get_log(cxlds, cmd); 217 break; 218 case CXL_MBOX_OP_IDENTIFY: 219 rc = mock_id(cxlds, cmd); 220 break; 221 case CXL_MBOX_OP_GET_LSA: 222 rc = mock_get_lsa(cxlds, cmd); 223 break; 224 case CXL_MBOX_OP_SET_LSA: 225 rc = mock_set_lsa(cxlds, cmd); 226 break; 227 case CXL_MBOX_OP_GET_HEALTH_INFO: 228 rc = mock_health_info(cxlds, cmd); 229 break; 230 default: 231 break; 232 } 233 234 dev_dbg(dev, "opcode: %#x sz_in: %zd sz_out: %zd rc: %d\n", cmd->opcode, 235 cmd->size_in, cmd->size_out, rc); 236 237 return rc; 238 } 239 240 static int cxl_mock_wait_media_ready(struct cxl_dev_state *cxlds) 241 { 242 msleep(100); 243 return 0; 244 } 245 246 static void label_area_release(void *lsa) 247 { 248 vfree(lsa); 249 } 250 251 static void mock_validate_dvsec_ranges(struct cxl_dev_state *cxlds) 252 { 253 struct cxl_endpoint_dvsec_info *info; 254 255 info = &cxlds->info; 256 info->mem_enabled = true; 257 } 258 259 static int cxl_mock_mem_probe(struct platform_device *pdev) 260 { 261 struct device *dev = &pdev->dev; 262 struct cxl_memdev *cxlmd; 263 struct cxl_dev_state *cxlds; 264 void *lsa; 265 int rc; 266 267 lsa = vmalloc(LSA_SIZE); 268 if (!lsa) 269 return -ENOMEM; 270 rc = devm_add_action_or_reset(dev, label_area_release, lsa); 271 if (rc) 272 return rc; 273 dev_set_drvdata(dev, lsa); 274 275 cxlds = cxl_dev_state_create(dev); 276 if (IS_ERR(cxlds)) 277 return PTR_ERR(cxlds); 278 279 cxlds->serial = pdev->id; 280 cxlds->mbox_send = cxl_mock_mbox_send; 281 cxlds->wait_media_ready = cxl_mock_wait_media_ready; 282 cxlds->payload_size = SZ_4K; 283 284 rc = cxl_enumerate_cmds(cxlds); 285 if (rc) 286 return rc; 287 288 rc = cxl_dev_state_identify(cxlds); 289 if (rc) 290 return rc; 291 292 rc = cxl_mem_create_range_info(cxlds); 293 if (rc) 294 return rc; 295 296 mock_validate_dvsec_ranges(cxlds); 297 298 cxlmd = devm_cxl_add_memdev(cxlds); 299 if (IS_ERR(cxlmd)) 300 return PTR_ERR(cxlmd); 301 302 if (range_len(&cxlds->pmem_range) && IS_ENABLED(CONFIG_CXL_PMEM)) 303 rc = devm_cxl_add_nvdimm(dev, cxlmd); 304 305 return 0; 306 } 307 308 static const struct platform_device_id cxl_mock_mem_ids[] = { 309 { .name = "cxl_mem", }, 310 { }, 311 }; 312 MODULE_DEVICE_TABLE(platform, cxl_mock_mem_ids); 313 314 static struct platform_driver cxl_mock_mem_driver = { 315 .probe = cxl_mock_mem_probe, 316 .id_table = cxl_mock_mem_ids, 317 .driver = { 318 .name = KBUILD_MODNAME, 319 }, 320 }; 321 322 module_platform_driver(cxl_mock_mem_driver); 323 MODULE_LICENSE("GPL v2"); 324 MODULE_IMPORT_NS(CXL); 325