1 /*
2 * Copyright (c) 2022 Intel Corporation
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/types.h>
8 #include <zephyr/ztest.h>
9
10 #include <zephyr/drivers/pcie/pcie.h>
11 #include <zephyr/drivers/smbus.h>
12
13 #include <intel_pch_smbus.h>
14
15 #include <zephyr/logging/log.h>
16 LOG_MODULE_REGISTER(emul, LOG_LEVEL_DBG);
17
18 #include "emul.h"
19
20 /**
21 * Emulate Intel PCH Host Controller hardware as PCI device with I/O access
22 */
23
24 /* PCI Configuration space */
25 uint32_t pci_config_area[32] = {
26 [PCIE_CONF_CMDSTAT] = PCIE_CONF_CMDSTAT_INTERRUPT, /* Mark INT */
27 [8] = 1, /* I/O BAR */
28 [16] = 1, /* Enable SMBus */
29 };
30
31 /* I/O and MMIO registers */
32 uint8_t io_area[24] = {
33 0,
34 };
35
36 struct e32_block {
37 uint8_t buf[SMBUS_BLOCK_BYTES_MAX];
38 uint8_t offset;
39 } e32;
40
41 /**
42 * Emulate SMBus peripheral device, connected to the bus as
43 * simple EEPROM device of size 256 bytes
44 */
45
46 /* List of peripheral devices registered */
47 sys_slist_t peripherals;
48
emul_register_smbus_peripheral(struct smbus_peripheral * peripheral)49 void emul_register_smbus_peripheral(struct smbus_peripheral *peripheral)
50 {
51 sys_slist_prepend(&peripherals, &peripheral->node);
52 }
53
emul_get_smbus_peripheral(uint8_t addr)54 static struct smbus_peripheral *emul_get_smbus_peripheral(uint8_t addr)
55 {
56 struct smbus_peripheral *peripheral, *tmp;
57
58 SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&peripherals, peripheral, tmp, node) {
59 if (peripheral->addr == addr) {
60 return peripheral;
61 }
62 }
63
64 return NULL;
65 }
66
peripheral_handle_smbalert(void)67 static bool peripheral_handle_smbalert(void)
68 {
69 struct smbus_peripheral *peripheral, *tmp, *found = NULL;
70
71 SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&peripherals, peripheral, tmp, node) {
72 if (peripheral->smbalert && !peripheral->smbalert_handled) {
73 found = peripheral;
74 }
75 }
76
77 if (found == NULL) {
78 LOG_WRN("No (more) smbalert handlers found");
79 return false;
80 }
81
82 LOG_DBG("Return own address: 0x%02x", found->addr);
83
84 io_area[PCH_SMBUS_HD0] = found->addr;
85 found->smbalert_handled = true;
86
87 return true;
88 }
89
peripheral_handle_host_notify(void)90 bool peripheral_handle_host_notify(void)
91 {
92 struct smbus_peripheral *peripheral, *tmp;
93
94 SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&peripherals, peripheral, tmp, node) {
95 if (peripheral->host_notify) {
96 LOG_DBG("Save own peripheral address to NDA");
97 io_area[PCH_SMBUS_NDA] = peripheral->addr << 1;
98
99 return true;
100 }
101 }
102
103 return false;
104 }
105
peripheral_write(uint8_t reg,uint8_t value)106 static void peripheral_write(uint8_t reg, uint8_t value)
107 {
108 uint8_t addr = PCH_SMBUS_TSA_ADDR_GET(io_area[PCH_SMBUS_TSA]);
109 struct smbus_peripheral *peripheral;
110
111 peripheral = emul_get_smbus_peripheral(addr);
112 if (peripheral) {
113 peripheral->raw_data[reg] = value;
114 LOG_DBG("peripheral: [0x%02x] <= 0x%02x", reg, value);
115 } else {
116 LOG_ERR("Peripheral not found, addr 0x%02x", addr);
117 }
118 }
119
peripheral_read(uint8_t reg,uint8_t * value)120 static void peripheral_read(uint8_t reg, uint8_t *value)
121 {
122 uint8_t addr = PCH_SMBUS_TSA_ADDR_GET(io_area[PCH_SMBUS_TSA]);
123 struct smbus_peripheral *peripheral;
124
125 peripheral = emul_get_smbus_peripheral(addr);
126 if (peripheral) {
127 *value = peripheral->raw_data[reg];
128 LOG_DBG("peripheral: [0x%02x] => 0x%02x", reg, *value);
129 } else {
130 LOG_ERR("Peripheral not found, addr 0x%02x", addr);
131 }
132 }
133
emul_start_smbus_protocol(void)134 static void emul_start_smbus_protocol(void)
135 {
136 uint8_t smbus_cmd = PCH_SMBUS_HCTL_CMD_GET(io_area[PCH_SMBUS_HCTL]);
137 bool write =
138 (io_area[PCH_SMBUS_TSA] & PCH_SMBUS_TSA_RW) == SMBUS_MSG_WRITE;
139 uint8_t addr = PCH_SMBUS_TSA_ADDR_GET(io_area[PCH_SMBUS_TSA]);
140 struct smbus_peripheral *peripheral;
141
142 LOG_DBG("Start SMBUS protocol");
143
144 if (unlikely(addr == SMBUS_ADDRESS_ARA)) {
145 if (peripheral_handle_smbalert()) {
146 goto isr;
147 }
148 }
149
150 peripheral = emul_get_smbus_peripheral(addr);
151 if (peripheral == NULL) {
152 LOG_WRN("Set Device Error");
153 emul_set_io(emul_get_io(PCH_SMBUS_HSTS) |
154 PCH_SMBUS_HSTS_DEV_ERROR, PCH_SMBUS_HSTS);
155 goto isr;
156 }
157
158 switch (smbus_cmd) {
159 case PCH_SMBUS_HCTL_CMD_QUICK:
160 LOG_DBG("Quick command");
161 break;
162 case PCH_SMBUS_HCTL_CMD_BYTE:
163 if (write) {
164 LOG_DBG("Byte Write command");
165 peripheral_write(0, io_area[PCH_SMBUS_HCMD]);
166 } else {
167 LOG_DBG("Byte Read command");
168 peripheral_read(0, &io_area[PCH_SMBUS_HD0]);
169 }
170 break;
171 case PCH_SMBUS_HCTL_CMD_BYTE_DATA:
172 if (write) {
173 LOG_DBG("Byte Data Write command");
174 peripheral_write(io_area[PCH_SMBUS_HCMD],
175 io_area[PCH_SMBUS_HD0]);
176 } else {
177 LOG_DBG("Byte Data Read command");
178 peripheral_read(io_area[PCH_SMBUS_HCMD],
179 &io_area[PCH_SMBUS_HD0]);
180 }
181 break;
182 case PCH_SMBUS_HCTL_CMD_WORD_DATA:
183 if (write) {
184 LOG_DBG("Word Data Write command");
185 peripheral_write(io_area[PCH_SMBUS_HCMD],
186 io_area[PCH_SMBUS_HD0]);
187 peripheral_write(io_area[PCH_SMBUS_HCMD] + 1,
188 io_area[PCH_SMBUS_HD1]);
189
190 } else {
191 LOG_DBG("Word Data Read command");
192 peripheral_read(io_area[PCH_SMBUS_HCMD],
193 &io_area[PCH_SMBUS_HD0]);
194 peripheral_read(io_area[PCH_SMBUS_HCMD] + 1,
195 &io_area[PCH_SMBUS_HD1]);
196 }
197 break;
198 case PCH_SMBUS_HCTL_CMD_PROC_CALL:
199 if (!write) {
200 LOG_ERR("Incorrect operation flag");
201 return;
202 }
203
204 LOG_DBG("Process Call command");
205
206 peripheral_write(io_area[PCH_SMBUS_HCMD],
207 io_area[PCH_SMBUS_HD0]);
208 peripheral_write(io_area[PCH_SMBUS_HCMD] + 1,
209 io_area[PCH_SMBUS_HD1]);
210
211 /**
212 * For the testing purposes implement data
213 * swap for the Proc Call, that would be
214 * easy for testing.
215 *
216 * Note: real device should have some other
217 * logic for Proc Call.
218 */
219 peripheral_read(io_area[PCH_SMBUS_HCMD],
220 &io_area[PCH_SMBUS_HD1]);
221 peripheral_read(io_area[PCH_SMBUS_HCMD] + 1,
222 &io_area[PCH_SMBUS_HD0]);
223 break;
224 case PCH_SMBUS_HCTL_CMD_BLOCK:
225 if (write) {
226 uint8_t count = io_area[PCH_SMBUS_HD0];
227 uint8_t reg = io_area[PCH_SMBUS_HCMD];
228
229 LOG_DBG("Block Write command");
230
231 if (count > SMBUS_BLOCK_BYTES_MAX) {
232 return;
233 }
234
235 for (int i = 0; i < count; i++) {
236 peripheral_write(reg++, e32.buf[i]);
237 }
238 } else {
239 /**
240 * count is set by peripheral device, just
241 * assume it to be maximum block count
242 */
243 uint8_t count = SMBUS_BLOCK_BYTES_MAX;
244 uint8_t reg = io_area[PCH_SMBUS_HCMD];
245
246 LOG_DBG("Block Read command");
247
248 for (int i = 0; i < count; i++) {
249 peripheral_read(reg++, &e32.buf[i]);
250 }
251
252 /* Set count */
253 io_area[PCH_SMBUS_HD0] = count;
254 }
255 break;
256 case PCH_SMBUS_HCTL_CMD_BLOCK_PROC:
257 if (!write) {
258 LOG_ERR("Incorrect operation flag");
259 } else {
260 uint8_t snd_count = io_area[PCH_SMBUS_HD0];
261 uint8_t reg = io_area[PCH_SMBUS_HCMD];
262 uint8_t rcv_count;
263
264 LOG_DBG("Block Process Call command");
265
266 if (snd_count > SMBUS_BLOCK_BYTES_MAX) {
267 return;
268 }
269
270 /**
271 * Make Block Process Call swap block buffer bytes
272 * for testing purposes only, return the same "count"
273 * bytes
274 */
275 for (int i = 0; i < snd_count; i++) {
276 peripheral_write(reg++, e32.buf[i]);
277 }
278
279 rcv_count = snd_count;
280 if (snd_count + rcv_count > SMBUS_BLOCK_BYTES_MAX) {
281 return;
282 }
283
284 for (int i = 0; i < rcv_count; i++) {
285 peripheral_read(--reg, &e32.buf[i]);
286 }
287
288 /* Clear offset count */
289 e32.offset = 0;
290
291 /* Set count */
292 io_area[PCH_SMBUS_HD0] = rcv_count;
293 }
294 break;
295 default:
296 LOG_ERR("Protocol is not implemented yet in emul");
297 break;
298 }
299
300 isr:
301 if (io_area[PCH_SMBUS_HCTL] & PCH_SMBUS_HCTL_INTR_EN) {
302 /* Fire emulated interrupt if enabled */
303 run_isr(EMUL_SMBUS_INTR);
304 }
305 }
306
emul_evaluate_write(uint8_t value,io_port_t addr)307 static void emul_evaluate_write(uint8_t value, io_port_t addr)
308 {
309 switch (addr) {
310 case PCH_SMBUS_HCTL:
311 if (value & PCH_SMBUS_HCTL_START) {
312 /* This is write only */
313 io_area[PCH_SMBUS_HCTL] = value & ~PCH_SMBUS_HCTL_START;
314
315 emul_start_smbus_protocol();
316 }
317 break;
318 default:
319 break;
320 }
321 }
322
pch_get_reg_name(uint8_t reg)323 static const char *pch_get_reg_name(uint8_t reg)
324 {
325 switch (reg) {
326 case PCH_SMBUS_HSTS:
327 return "HSTS";
328 case PCH_SMBUS_HCTL:
329 return "HCTL";
330 case PCH_SMBUS_HCMD:
331 return "HCMD";
332 case PCH_SMBUS_TSA:
333 return "TSA";
334 case PCH_SMBUS_HD0:
335 return "HD0";
336 case PCH_SMBUS_HD1:
337 return "HD1";
338 case PCH_SMBUS_HBD:
339 return "HBD";
340 case PCH_SMBUS_PEC:
341 return "PEC";
342 case PCH_SMBUS_RSA:
343 return "RSA";
344 case PCH_SMBUS_SD:
345 return "SD";
346 case PCH_SMBUS_AUXS:
347 return "AUXS";
348 case PCH_SMBUS_AUXC:
349 return "AUXC";
350 case PCH_SMBUS_SMLC:
351 return "SMLC";
352 case PCH_SMBUS_SMBC:
353 return "SMBC";
354 case PCH_SMBUS_SSTS:
355 return "SSTS";
356 case PCH_SMBUS_SCMD:
357 return "SCMD";
358 case PCH_SMBUS_NDA:
359 return "NDA";
360 case PCH_SMBUS_NDLB:
361 return "NDLB";
362 case PCH_SMBUS_NDHB:
363 return "NDHB";
364 default:
365 return "Unknown";
366 }
367 }
368
emul_pci_read(unsigned int reg)369 uint32_t emul_pci_read(unsigned int reg)
370 {
371 LOG_DBG("PCI [%x] => 0x%x", reg, pci_config_area[reg]);
372 return pci_config_area[reg];
373 }
374
emul_pci_write(pcie_bdf_t bdf,unsigned int reg,uint32_t value)375 void emul_pci_write(pcie_bdf_t bdf, unsigned int reg, uint32_t value)
376 {
377 LOG_DBG("PCI [%x] <= 0x%x", reg, value);
378 pci_config_area[reg] = value;
379 }
380
381 /* This function is used to set registers for emulation purpose */
emul_set_io(uint8_t value,io_port_t addr)382 void emul_set_io(uint8_t value, io_port_t addr)
383 {
384 io_area[addr] = value;
385 }
386
emul_get_io(io_port_t addr)387 uint8_t emul_get_io(io_port_t addr)
388 {
389 return io_area[addr];
390 }
391
emul_out8(uint8_t value,io_port_t addr)392 void emul_out8(uint8_t value, io_port_t addr)
393 {
394 switch (addr) {
395 case PCH_SMBUS_HSTS:
396 /* Writing clears status bits */
397 io_area[addr] &= ~value;
398 break;
399 case PCH_SMBUS_SSTS:
400 /* Writing clears status bits */
401 io_area[addr] &= ~value;
402 break;
403 case PCH_SMBUS_HBD:
404 /* Using internal E32 buffer offset */
405 e32.buf[e32.offset++] = value;
406 break;
407 case PCH_SMBUS_AUXC:
408 if (value & PCH_SMBUS_AUXC_EN_32BUF) {
409 LOG_DBG("Enabled 32 bit buffer block mode");
410 }
411 io_area[addr] = value;
412 break;
413 default:
414 io_area[addr] = value;
415 break;
416 }
417
418 LOG_DBG("I/O [%s] <= 0x%x => 0x%x", pch_get_reg_name(addr), value,
419 io_area[addr]);
420
421 /**
422 * Evaluate should decide about starting actual SMBus
423 * protocol transaction emulation
424 */
425 emul_evaluate_write(value, addr);
426 }
427
emul_in8(io_port_t addr)428 uint8_t emul_in8(io_port_t addr)
429 {
430 uint8_t value;
431
432 switch (addr) {
433 case PCH_SMBUS_HBD:
434 /* Using internal E32 buffer offset */
435 value = e32.buf[e32.offset++];
436 break;
437 case PCH_SMBUS_HCTL:
438 /* Clear e32 block buffer offset */
439 e32.offset = 0;
440 LOG_WRN("E32 buffer offset is cleared");
441 value = io_area[addr];
442 break;
443 default:
444 value = io_area[addr];
445 break;
446 }
447
448 LOG_DBG("I/O [%s] => 0x%x", pch_get_reg_name(addr), value);
449
450 return value;
451 }
452