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 DEV_SIZE SZ_2G 14 #define EFFECT(x) (1U << x) 15 16 static struct cxl_cel_entry mock_cel[] = { 17 { 18 .opcode = cpu_to_le16(CXL_MBOX_OP_GET_SUPPORTED_LOGS), 19 .effect = cpu_to_le16(0), 20 }, 21 { 22 .opcode = cpu_to_le16(CXL_MBOX_OP_IDENTIFY), 23 .effect = cpu_to_le16(0), 24 }, 25 { 26 .opcode = cpu_to_le16(CXL_MBOX_OP_GET_LSA), 27 .effect = cpu_to_le16(0), 28 }, 29 { 30 .opcode = cpu_to_le16(CXL_MBOX_OP_GET_PARTITION_INFO), 31 .effect = cpu_to_le16(0), 32 }, 33 { 34 .opcode = cpu_to_le16(CXL_MBOX_OP_SET_LSA), 35 .effect = cpu_to_le16(EFFECT(1) | EFFECT(2)), 36 }, 37 { 38 .opcode = cpu_to_le16(CXL_MBOX_OP_GET_HEALTH_INFO), 39 .effect = cpu_to_le16(0), 40 }, 41 }; 42 43 /* See CXL 2.0 Table 181 Get Health Info Output Payload */ 44 struct cxl_mbox_health_info { 45 u8 health_status; 46 u8 media_status; 47 u8 ext_status; 48 u8 life_used; 49 __le16 temperature; 50 __le32 dirty_shutdowns; 51 __le32 volatile_errors; 52 __le32 pmem_errors; 53 } __packed; 54 55 static struct { 56 struct cxl_mbox_get_supported_logs gsl; 57 struct cxl_gsl_entry entry; 58 } mock_gsl_payload = { 59 .gsl = { 60 .entries = cpu_to_le16(1), 61 }, 62 .entry = { 63 .uuid = DEFINE_CXL_CEL_UUID, 64 .size = cpu_to_le32(sizeof(mock_cel)), 65 }, 66 }; 67 68 static int mock_gsl(struct cxl_mbox_cmd *cmd) 69 { 70 if (cmd->size_out < sizeof(mock_gsl_payload)) 71 return -EINVAL; 72 73 memcpy(cmd->payload_out, &mock_gsl_payload, sizeof(mock_gsl_payload)); 74 cmd->size_out = sizeof(mock_gsl_payload); 75 76 return 0; 77 } 78 79 static int mock_get_log(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd) 80 { 81 struct cxl_mbox_get_log *gl = cmd->payload_in; 82 u32 offset = le32_to_cpu(gl->offset); 83 u32 length = le32_to_cpu(gl->length); 84 uuid_t uuid = DEFINE_CXL_CEL_UUID; 85 void *data = &mock_cel; 86 87 if (cmd->size_in < sizeof(*gl)) 88 return -EINVAL; 89 if (length > cxlds->payload_size) 90 return -EINVAL; 91 if (offset + length > sizeof(mock_cel)) 92 return -EINVAL; 93 if (!uuid_equal(&gl->uuid, &uuid)) 94 return -EINVAL; 95 if (length > cmd->size_out) 96 return -EINVAL; 97 98 memcpy(cmd->payload_out, data + offset, length); 99 100 return 0; 101 } 102 103 static int mock_id(struct cxl_dev_state *cxlds, struct cxl_mbox_cmd *cmd) 104 { 105 struct cxl_mbox_identify id = { 106 .fw_revision = { "mock fw v1 " }, 107 .lsa_size = cpu_to_le32(LSA_SIZE), 108 .partition_align = 109 cpu_to_le64(SZ_256M / CXL_CAPACITY_MULTIPLIER), 110 .total_capacity = 111 cpu_to_le64(DEV_SIZE / CXL_CAPACITY_MULTIPLIER), 112 }; 113 114 if (cmd->size_out < sizeof(id)) 115 return -EINVAL; 116 117 memcpy(cmd->payload_out, &id, sizeof(id)); 118 119 return 0; 120 } 121 122 static int mock_partition_info(struct cxl_dev_state *cxlds, 123 struct cxl_mbox_cmd *cmd) 124 { 125 struct cxl_mbox_get_partition_info pi = { 126 .active_volatile_cap = 127 cpu_to_le64(DEV_SIZE / 2 / CXL_CAPACITY_MULTIPLIER), 128 .active_persistent_cap = 129 cpu_to_le64(DEV_SIZE / 2 / CXL_CAPACITY_MULTIPLIER), 130 }; 131 132 if (cmd->size_out < sizeof(pi)) 133 return -EINVAL; 134 135 memcpy(cmd->payload_out, &pi, sizeof(pi)); 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_GET_PARTITION_INFO: 225 rc = mock_partition_info(cxlds, cmd); 226 break; 227 case CXL_MBOX_OP_SET_LSA: 228 rc = mock_set_lsa(cxlds, cmd); 229 break; 230 case CXL_MBOX_OP_GET_HEALTH_INFO: 231 rc = mock_health_info(cxlds, cmd); 232 break; 233 default: 234 break; 235 } 236 237 dev_dbg(dev, "opcode: %#x sz_in: %zd sz_out: %zd rc: %d\n", cmd->opcode, 238 cmd->size_in, cmd->size_out, rc); 239 240 return rc; 241 } 242 243 static void label_area_release(void *lsa) 244 { 245 vfree(lsa); 246 } 247 248 static int cxl_mock_mem_probe(struct platform_device *pdev) 249 { 250 struct device *dev = &pdev->dev; 251 struct cxl_memdev *cxlmd; 252 struct cxl_dev_state *cxlds; 253 void *lsa; 254 int rc; 255 256 lsa = vmalloc(LSA_SIZE); 257 if (!lsa) 258 return -ENOMEM; 259 rc = devm_add_action_or_reset(dev, label_area_release, lsa); 260 if (rc) 261 return rc; 262 dev_set_drvdata(dev, lsa); 263 264 cxlds = cxl_dev_state_create(dev); 265 if (IS_ERR(cxlds)) 266 return PTR_ERR(cxlds); 267 268 cxlds->serial = pdev->id; 269 cxlds->mbox_send = cxl_mock_mbox_send; 270 cxlds->payload_size = SZ_4K; 271 272 rc = cxl_enumerate_cmds(cxlds); 273 if (rc) 274 return rc; 275 276 rc = cxl_dev_state_identify(cxlds); 277 if (rc) 278 return rc; 279 280 rc = cxl_mem_create_range_info(cxlds); 281 if (rc) 282 return rc; 283 284 cxlmd = devm_cxl_add_memdev(cxlds); 285 if (IS_ERR(cxlmd)) 286 return PTR_ERR(cxlmd); 287 288 if (resource_size(&cxlds->pmem_res) && IS_ENABLED(CONFIG_CXL_PMEM)) 289 rc = devm_cxl_add_nvdimm(dev, cxlmd); 290 291 return 0; 292 } 293 294 static const struct platform_device_id cxl_mock_mem_ids[] = { 295 { .name = "cxl_mem", }, 296 { }, 297 }; 298 MODULE_DEVICE_TABLE(platform, cxl_mock_mem_ids); 299 300 static struct platform_driver cxl_mock_mem_driver = { 301 .probe = cxl_mock_mem_probe, 302 .id_table = cxl_mock_mem_ids, 303 .driver = { 304 .name = KBUILD_MODNAME, 305 }, 306 }; 307 308 module_platform_driver(cxl_mock_mem_driver); 309 MODULE_LICENSE("GPL v2"); 310 MODULE_IMPORT_NS(CXL); 311