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