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