]>
Commit | Line | Data |
---|---|---|
22f90bcb BG |
1 | /* |
2 | * Emulation of Allwinner EMAC Fast Ethernet controller and | |
3 | * Realtek RTL8201CP PHY | |
4 | * | |
5 | * Copyright (C) 2014 Beniamino Galvani <[email protected]> | |
6 | * | |
7 | * This model is based on reverse-engineering of Linux kernel driver. | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License version 2 as | |
11 | * published by the Free Software Foundation. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
18 | */ | |
0b8fa32f | 19 | |
8ef94f0b | 20 | #include "qemu/osdep.h" |
22f90bcb BG |
21 | #include "hw/sysbus.h" |
22 | #include "net/net.h" | |
23 | #include "qemu/fifo8.h" | |
64552b6b | 24 | #include "hw/irq.h" |
22f90bcb | 25 | #include "hw/net/allwinner_emac.h" |
03dd024f | 26 | #include "qemu/log.h" |
0b8fa32f | 27 | #include "qemu/module.h" |
22f90bcb BG |
28 | #include <zlib.h> |
29 | ||
30 | static uint8_t padding[60]; | |
31 | ||
32 | static void mii_set_link(RTL8201CPState *mii, bool link_ok) | |
33 | { | |
34 | if (link_ok) { | |
103db49a | 35 | mii->bmsr |= MII_BMSR_LINK_ST | MII_BMSR_AN_COMP; |
22f90bcb BG |
36 | mii->anlpar |= MII_ANAR_TXFD | MII_ANAR_10FD | MII_ANAR_10 | |
37 | MII_ANAR_CSMACD; | |
38 | } else { | |
103db49a | 39 | mii->bmsr &= ~(MII_BMSR_LINK_ST | MII_BMSR_AN_COMP); |
22f90bcb BG |
40 | mii->anlpar = MII_ANAR_TX; |
41 | } | |
42 | } | |
43 | ||
44 | static void mii_reset(RTL8201CPState *mii, bool link_ok) | |
45 | { | |
46 | mii->bmcr = MII_BMCR_FD | MII_BMCR_AUTOEN | MII_BMCR_SPEED; | |
47 | mii->bmsr = MII_BMSR_100TX_FD | MII_BMSR_100TX_HD | MII_BMSR_10T_FD | | |
48 | MII_BMSR_10T_HD | MII_BMSR_MFPS | MII_BMSR_AUTONEG; | |
49 | mii->anar = MII_ANAR_TXFD | MII_ANAR_TX | MII_ANAR_10FD | MII_ANAR_10 | | |
50 | MII_ANAR_CSMACD; | |
51 | mii->anlpar = MII_ANAR_TX; | |
52 | ||
53 | mii_set_link(mii, link_ok); | |
54 | } | |
55 | ||
56 | static uint16_t RTL8201CP_mdio_read(AwEmacState *s, uint8_t addr, uint8_t reg) | |
57 | { | |
58 | RTL8201CPState *mii = &s->mii; | |
59 | uint16_t ret = 0xffff; | |
60 | ||
61 | if (addr == s->phy_addr) { | |
62 | switch (reg) { | |
63 | case MII_BMCR: | |
64 | return mii->bmcr; | |
65 | case MII_BMSR: | |
66 | return mii->bmsr; | |
67 | case MII_PHYID1: | |
68 | return RTL8201CP_PHYID1; | |
69 | case MII_PHYID2: | |
70 | return RTL8201CP_PHYID2; | |
71 | case MII_ANAR: | |
72 | return mii->anar; | |
73 | case MII_ANLPAR: | |
74 | return mii->anlpar; | |
75 | case MII_ANER: | |
76 | case MII_NSR: | |
77 | case MII_LBREMR: | |
78 | case MII_REC: | |
79 | case MII_SNRDR: | |
80 | case MII_TEST: | |
81 | qemu_log_mask(LOG_UNIMP, | |
82 | "allwinner_emac: read from unimpl. mii reg 0x%x\n", | |
83 | reg); | |
84 | return 0; | |
85 | default: | |
86 | qemu_log_mask(LOG_GUEST_ERROR, | |
87 | "allwinner_emac: read from invalid mii reg 0x%x\n", | |
88 | reg); | |
89 | return 0; | |
90 | } | |
91 | } | |
92 | return ret; | |
93 | } | |
94 | ||
95 | static void RTL8201CP_mdio_write(AwEmacState *s, uint8_t addr, uint8_t reg, | |
96 | uint16_t value) | |
97 | { | |
98 | RTL8201CPState *mii = &s->mii; | |
99 | NetClientState *nc; | |
100 | ||
101 | if (addr == s->phy_addr) { | |
102 | switch (reg) { | |
103 | case MII_BMCR: | |
104 | if (value & MII_BMCR_RESET) { | |
105 | nc = qemu_get_queue(s->nic); | |
106 | mii_reset(mii, !nc->link_down); | |
107 | } else { | |
108 | mii->bmcr = value; | |
109 | } | |
110 | break; | |
111 | case MII_ANAR: | |
112 | mii->anar = value; | |
113 | break; | |
114 | case MII_BMSR: | |
115 | case MII_PHYID1: | |
116 | case MII_PHYID2: | |
117 | case MII_ANLPAR: | |
118 | case MII_ANER: | |
119 | qemu_log_mask(LOG_GUEST_ERROR, | |
120 | "allwinner_emac: write to read-only mii reg 0x%x\n", | |
121 | reg); | |
122 | break; | |
123 | case MII_NSR: | |
124 | case MII_LBREMR: | |
125 | case MII_REC: | |
126 | case MII_SNRDR: | |
127 | case MII_TEST: | |
128 | qemu_log_mask(LOG_UNIMP, | |
129 | "allwinner_emac: write to unimpl. mii reg 0x%x\n", | |
130 | reg); | |
131 | break; | |
132 | default: | |
133 | qemu_log_mask(LOG_GUEST_ERROR, | |
134 | "allwinner_emac: write to invalid mii reg 0x%x\n", | |
135 | reg); | |
136 | } | |
137 | } | |
138 | } | |
139 | ||
140 | static void aw_emac_update_irq(AwEmacState *s) | |
141 | { | |
142 | qemu_set_irq(s->irq, (s->int_sta & s->int_ctl) != 0); | |
143 | } | |
144 | ||
145 | static void aw_emac_tx_reset(AwEmacState *s, int chan) | |
146 | { | |
147 | fifo8_reset(&s->tx_fifo[chan]); | |
148 | s->tx_length[chan] = 0; | |
149 | } | |
150 | ||
151 | static void aw_emac_rx_reset(AwEmacState *s) | |
152 | { | |
153 | fifo8_reset(&s->rx_fifo); | |
154 | s->rx_num_packets = 0; | |
155 | s->rx_packet_size = 0; | |
156 | s->rx_packet_pos = 0; | |
157 | } | |
158 | ||
159 | static void fifo8_push_word(Fifo8 *fifo, uint32_t val) | |
160 | { | |
161 | fifo8_push(fifo, val); | |
162 | fifo8_push(fifo, val >> 8); | |
163 | fifo8_push(fifo, val >> 16); | |
164 | fifo8_push(fifo, val >> 24); | |
165 | } | |
166 | ||
167 | static uint32_t fifo8_pop_word(Fifo8 *fifo) | |
168 | { | |
169 | uint32_t ret; | |
170 | ||
171 | ret = fifo8_pop(fifo); | |
172 | ret |= fifo8_pop(fifo) << 8; | |
173 | ret |= fifo8_pop(fifo) << 16; | |
174 | ret |= fifo8_pop(fifo) << 24; | |
175 | ||
176 | return ret; | |
177 | } | |
178 | ||
179 | static int aw_emac_can_receive(NetClientState *nc) | |
180 | { | |
181 | AwEmacState *s = qemu_get_nic_opaque(nc); | |
182 | ||
183 | /* | |
184 | * To avoid packet drops, allow reception only when there is space | |
185 | * for a full frame: 1522 + 8 (rx headers) + 2 (padding). | |
186 | */ | |
187 | return (s->ctl & EMAC_CTL_RX_EN) && (fifo8_num_free(&s->rx_fifo) >= 1532); | |
188 | } | |
189 | ||
190 | static ssize_t aw_emac_receive(NetClientState *nc, const uint8_t *buf, | |
191 | size_t size) | |
192 | { | |
193 | AwEmacState *s = qemu_get_nic_opaque(nc); | |
194 | Fifo8 *fifo = &s->rx_fifo; | |
195 | size_t padded_size, total_size; | |
196 | uint32_t crc; | |
197 | ||
198 | padded_size = size > 60 ? size : 60; | |
199 | total_size = QEMU_ALIGN_UP(RX_HDR_SIZE + padded_size + CRC_SIZE, 4); | |
200 | ||
201 | if (!(s->ctl & EMAC_CTL_RX_EN) || (fifo8_num_free(fifo) < total_size)) { | |
202 | return -1; | |
203 | } | |
204 | ||
205 | fifo8_push_word(fifo, EMAC_UNDOCUMENTED_MAGIC); | |
206 | fifo8_push_word(fifo, EMAC_RX_HEADER(padded_size + CRC_SIZE, | |
207 | EMAC_RX_IO_DATA_STATUS_OK)); | |
208 | fifo8_push_all(fifo, buf, size); | |
209 | crc = crc32(~0, buf, size); | |
210 | ||
211 | if (padded_size != size) { | |
212 | fifo8_push_all(fifo, padding, padded_size - size); | |
213 | crc = crc32(crc, padding, padded_size - size); | |
214 | } | |
215 | ||
216 | fifo8_push_word(fifo, crc); | |
217 | fifo8_push_all(fifo, padding, QEMU_ALIGN_UP(padded_size, 4) - padded_size); | |
218 | s->rx_num_packets++; | |
219 | ||
220 | s->int_sta |= EMAC_INT_RX; | |
221 | aw_emac_update_irq(s); | |
222 | ||
223 | return size; | |
224 | } | |
225 | ||
22f90bcb BG |
226 | static void aw_emac_reset(DeviceState *dev) |
227 | { | |
228 | AwEmacState *s = AW_EMAC(dev); | |
229 | NetClientState *nc = qemu_get_queue(s->nic); | |
230 | ||
231 | s->ctl = 0; | |
232 | s->tx_mode = 0; | |
233 | s->int_ctl = 0; | |
234 | s->int_sta = 0; | |
235 | s->tx_channel = 0; | |
236 | s->phy_target = 0; | |
237 | ||
238 | aw_emac_tx_reset(s, 0); | |
239 | aw_emac_tx_reset(s, 1); | |
240 | aw_emac_rx_reset(s); | |
241 | ||
242 | mii_reset(&s->mii, !nc->link_down); | |
243 | } | |
244 | ||
245 | static uint64_t aw_emac_read(void *opaque, hwaddr offset, unsigned size) | |
246 | { | |
247 | AwEmacState *s = opaque; | |
248 | Fifo8 *fifo = &s->rx_fifo; | |
249 | NetClientState *nc; | |
250 | uint64_t ret; | |
251 | ||
252 | switch (offset) { | |
253 | case EMAC_CTL_REG: | |
254 | return s->ctl; | |
255 | case EMAC_TX_MODE_REG: | |
256 | return s->tx_mode; | |
257 | case EMAC_TX_INS_REG: | |
258 | return s->tx_channel; | |
259 | case EMAC_RX_CTL_REG: | |
260 | return s->rx_ctl; | |
261 | case EMAC_RX_IO_DATA_REG: | |
262 | if (!s->rx_num_packets) { | |
263 | qemu_log_mask(LOG_GUEST_ERROR, | |
264 | "Read IO data register when no packet available"); | |
265 | return 0; | |
266 | } | |
267 | ||
268 | ret = fifo8_pop_word(fifo); | |
269 | ||
270 | switch (s->rx_packet_pos) { | |
271 | case 0: /* Word is magic header */ | |
272 | s->rx_packet_pos += 4; | |
273 | break; | |
274 | case 4: /* Word is rx info header */ | |
275 | s->rx_packet_pos += 4; | |
276 | s->rx_packet_size = QEMU_ALIGN_UP(extract32(ret, 0, 16), 4); | |
277 | break; | |
278 | default: /* Word is packet data */ | |
279 | s->rx_packet_pos += 4; | |
280 | s->rx_packet_size -= 4; | |
281 | ||
282 | if (!s->rx_packet_size) { | |
283 | s->rx_packet_pos = 0; | |
284 | s->rx_num_packets--; | |
285 | nc = qemu_get_queue(s->nic); | |
286 | if (aw_emac_can_receive(nc)) { | |
287 | qemu_flush_queued_packets(nc); | |
288 | } | |
289 | } | |
290 | } | |
291 | return ret; | |
292 | case EMAC_RX_FBC_REG: | |
293 | return s->rx_num_packets; | |
294 | case EMAC_INT_CTL_REG: | |
295 | return s->int_ctl; | |
296 | case EMAC_INT_STA_REG: | |
297 | return s->int_sta; | |
298 | case EMAC_MAC_MRDD_REG: | |
299 | return RTL8201CP_mdio_read(s, | |
300 | extract32(s->phy_target, PHY_ADDR_SHIFT, 8), | |
301 | extract32(s->phy_target, PHY_REG_SHIFT, 8)); | |
302 | default: | |
303 | qemu_log_mask(LOG_UNIMP, | |
304 | "allwinner_emac: read access to unknown register 0x" | |
305 | TARGET_FMT_plx "\n", offset); | |
306 | ret = 0; | |
307 | } | |
308 | ||
309 | return ret; | |
310 | } | |
311 | ||
312 | static void aw_emac_write(void *opaque, hwaddr offset, uint64_t value, | |
313 | unsigned size) | |
314 | { | |
315 | AwEmacState *s = opaque; | |
316 | Fifo8 *fifo; | |
317 | NetClientState *nc = qemu_get_queue(s->nic); | |
318 | int chan; | |
319 | ||
320 | switch (offset) { | |
321 | case EMAC_CTL_REG: | |
322 | if (value & EMAC_CTL_RESET) { | |
323 | aw_emac_reset(DEVICE(s)); | |
324 | value &= ~EMAC_CTL_RESET; | |
325 | } | |
326 | s->ctl = value; | |
327 | if (aw_emac_can_receive(nc)) { | |
328 | qemu_flush_queued_packets(nc); | |
329 | } | |
330 | break; | |
331 | case EMAC_TX_MODE_REG: | |
332 | s->tx_mode = value; | |
333 | break; | |
334 | case EMAC_TX_CTL0_REG: | |
335 | case EMAC_TX_CTL1_REG: | |
336 | chan = (offset == EMAC_TX_CTL0_REG ? 0 : 1); | |
337 | if ((value & 1) && (s->ctl & EMAC_CTL_TX_EN)) { | |
338 | uint32_t len, ret; | |
339 | const uint8_t *data; | |
340 | ||
341 | fifo = &s->tx_fifo[chan]; | |
342 | len = s->tx_length[chan]; | |
343 | ||
344 | if (len > fifo8_num_used(fifo)) { | |
345 | len = fifo8_num_used(fifo); | |
346 | qemu_log_mask(LOG_GUEST_ERROR, | |
347 | "allwinner_emac: TX length > fifo data length\n"); | |
348 | } | |
349 | if (len > 0) { | |
350 | data = fifo8_pop_buf(fifo, len, &ret); | |
351 | qemu_send_packet(nc, data, ret); | |
352 | aw_emac_tx_reset(s, chan); | |
353 | /* Raise TX interrupt */ | |
354 | s->int_sta |= EMAC_INT_TX_CHAN(chan); | |
355 | aw_emac_update_irq(s); | |
356 | } | |
357 | } | |
358 | break; | |
359 | case EMAC_TX_INS_REG: | |
360 | s->tx_channel = value < NUM_TX_FIFOS ? value : 0; | |
361 | break; | |
362 | case EMAC_TX_PL0_REG: | |
363 | case EMAC_TX_PL1_REG: | |
364 | chan = (offset == EMAC_TX_PL0_REG ? 0 : 1); | |
365 | if (value > TX_FIFO_SIZE) { | |
366 | qemu_log_mask(LOG_GUEST_ERROR, | |
367 | "allwinner_emac: invalid TX frame length %d\n", | |
368 | (int)value); | |
369 | value = TX_FIFO_SIZE; | |
370 | } | |
371 | s->tx_length[chan] = value; | |
372 | break; | |
373 | case EMAC_TX_IO_DATA_REG: | |
374 | fifo = &s->tx_fifo[s->tx_channel]; | |
375 | if (fifo8_num_free(fifo) < 4) { | |
376 | qemu_log_mask(LOG_GUEST_ERROR, | |
377 | "allwinner_emac: TX data overruns fifo\n"); | |
378 | break; | |
379 | } | |
380 | fifo8_push_word(fifo, value); | |
381 | break; | |
382 | case EMAC_RX_CTL_REG: | |
383 | s->rx_ctl = value; | |
384 | break; | |
385 | case EMAC_RX_FBC_REG: | |
386 | if (value == 0) { | |
387 | aw_emac_rx_reset(s); | |
388 | } | |
389 | break; | |
390 | case EMAC_INT_CTL_REG: | |
391 | s->int_ctl = value; | |
6619bc5c | 392 | aw_emac_update_irq(s); |
22f90bcb BG |
393 | break; |
394 | case EMAC_INT_STA_REG: | |
395 | s->int_sta &= ~value; | |
6619bc5c | 396 | aw_emac_update_irq(s); |
22f90bcb BG |
397 | break; |
398 | case EMAC_MAC_MADR_REG: | |
399 | s->phy_target = value; | |
400 | break; | |
401 | case EMAC_MAC_MWTD_REG: | |
402 | RTL8201CP_mdio_write(s, extract32(s->phy_target, PHY_ADDR_SHIFT, 8), | |
403 | extract32(s->phy_target, PHY_REG_SHIFT, 8), value); | |
404 | break; | |
405 | default: | |
406 | qemu_log_mask(LOG_UNIMP, | |
407 | "allwinner_emac: write access to unknown register 0x" | |
408 | TARGET_FMT_plx "\n", offset); | |
409 | } | |
410 | } | |
411 | ||
412 | static void aw_emac_set_link(NetClientState *nc) | |
413 | { | |
414 | AwEmacState *s = qemu_get_nic_opaque(nc); | |
415 | ||
416 | mii_set_link(&s->mii, !nc->link_down); | |
417 | } | |
418 | ||
419 | static const MemoryRegionOps aw_emac_mem_ops = { | |
420 | .read = aw_emac_read, | |
421 | .write = aw_emac_write, | |
422 | .endianness = DEVICE_NATIVE_ENDIAN, | |
423 | .valid = { | |
424 | .min_access_size = 4, | |
425 | .max_access_size = 4, | |
426 | }, | |
427 | }; | |
428 | ||
429 | static NetClientInfo net_aw_emac_info = { | |
f394b2e2 | 430 | .type = NET_CLIENT_DRIVER_NIC, |
22f90bcb BG |
431 | .size = sizeof(NICState), |
432 | .can_receive = aw_emac_can_receive, | |
433 | .receive = aw_emac_receive, | |
22f90bcb BG |
434 | .link_status_changed = aw_emac_set_link, |
435 | }; | |
436 | ||
437 | static void aw_emac_init(Object *obj) | |
438 | { | |
439 | SysBusDevice *sbd = SYS_BUS_DEVICE(obj); | |
440 | AwEmacState *s = AW_EMAC(obj); | |
441 | ||
442 | memory_region_init_io(&s->iomem, OBJECT(s), &aw_emac_mem_ops, s, | |
443 | "aw_emac", 0x1000); | |
444 | sysbus_init_mmio(sbd, &s->iomem); | |
445 | sysbus_init_irq(sbd, &s->irq); | |
446 | } | |
447 | ||
448 | static void aw_emac_realize(DeviceState *dev, Error **errp) | |
449 | { | |
450 | AwEmacState *s = AW_EMAC(dev); | |
451 | ||
452 | qemu_macaddr_default_if_unset(&s->conf.macaddr); | |
453 | s->nic = qemu_new_nic(&net_aw_emac_info, &s->conf, | |
454 | object_get_typename(OBJECT(dev)), dev->id, s); | |
455 | qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a); | |
456 | ||
457 | fifo8_create(&s->rx_fifo, RX_FIFO_SIZE); | |
458 | fifo8_create(&s->tx_fifo[0], TX_FIFO_SIZE); | |
459 | fifo8_create(&s->tx_fifo[1], TX_FIFO_SIZE); | |
460 | } | |
461 | ||
462 | static Property aw_emac_properties[] = { | |
463 | DEFINE_NIC_PROPERTIES(AwEmacState, conf), | |
464 | DEFINE_PROP_UINT8("phy-addr", AwEmacState, phy_addr, 0), | |
465 | DEFINE_PROP_END_OF_LIST(), | |
466 | }; | |
467 | ||
468 | static const VMStateDescription vmstate_mii = { | |
469 | .name = "rtl8201cp", | |
470 | .version_id = 1, | |
471 | .minimum_version_id = 1, | |
472 | .fields = (VMStateField[]) { | |
473 | VMSTATE_UINT16(bmcr, RTL8201CPState), | |
474 | VMSTATE_UINT16(bmsr, RTL8201CPState), | |
475 | VMSTATE_UINT16(anar, RTL8201CPState), | |
476 | VMSTATE_UINT16(anlpar, RTL8201CPState), | |
477 | VMSTATE_END_OF_LIST() | |
478 | } | |
479 | }; | |
480 | ||
481 | static int aw_emac_post_load(void *opaque, int version_id) | |
482 | { | |
483 | AwEmacState *s = opaque; | |
484 | ||
485 | aw_emac_set_link(qemu_get_queue(s->nic)); | |
486 | ||
487 | return 0; | |
488 | } | |
489 | ||
490 | static const VMStateDescription vmstate_aw_emac = { | |
491 | .name = "allwinner_emac", | |
492 | .version_id = 1, | |
493 | .minimum_version_id = 1, | |
494 | .post_load = aw_emac_post_load, | |
495 | .fields = (VMStateField[]) { | |
496 | VMSTATE_STRUCT(mii, AwEmacState, 1, vmstate_mii, RTL8201CPState), | |
497 | VMSTATE_UINT32(ctl, AwEmacState), | |
498 | VMSTATE_UINT32(tx_mode, AwEmacState), | |
499 | VMSTATE_UINT32(rx_ctl, AwEmacState), | |
500 | VMSTATE_UINT32(int_ctl, AwEmacState), | |
501 | VMSTATE_UINT32(int_sta, AwEmacState), | |
502 | VMSTATE_UINT32(phy_target, AwEmacState), | |
503 | VMSTATE_FIFO8(rx_fifo, AwEmacState), | |
504 | VMSTATE_UINT32(rx_num_packets, AwEmacState), | |
505 | VMSTATE_UINT32(rx_packet_size, AwEmacState), | |
506 | VMSTATE_UINT32(rx_packet_pos, AwEmacState), | |
507 | VMSTATE_STRUCT_ARRAY(tx_fifo, AwEmacState, NUM_TX_FIFOS, 1, | |
508 | vmstate_fifo8, Fifo8), | |
509 | VMSTATE_UINT32_ARRAY(tx_length, AwEmacState, NUM_TX_FIFOS), | |
510 | VMSTATE_UINT32(tx_channel, AwEmacState), | |
511 | VMSTATE_END_OF_LIST() | |
512 | } | |
513 | }; | |
514 | ||
515 | static void aw_emac_class_init(ObjectClass *klass, void *data) | |
516 | { | |
517 | DeviceClass *dc = DEVICE_CLASS(klass); | |
518 | ||
519 | dc->realize = aw_emac_realize; | |
520 | dc->props = aw_emac_properties; | |
521 | dc->reset = aw_emac_reset; | |
522 | dc->vmsd = &vmstate_aw_emac; | |
523 | } | |
524 | ||
525 | static const TypeInfo aw_emac_info = { | |
526 | .name = TYPE_AW_EMAC, | |
527 | .parent = TYPE_SYS_BUS_DEVICE, | |
528 | .instance_size = sizeof(AwEmacState), | |
529 | .instance_init = aw_emac_init, | |
530 | .class_init = aw_emac_class_init, | |
531 | }; | |
532 | ||
533 | static void aw_emac_register_types(void) | |
534 | { | |
535 | type_register_static(&aw_emac_info); | |
536 | } | |
537 | ||
538 | type_init(aw_emac_register_types) |