Commit a3ea5df58845aac6e49ee36d4bef654e67b78c8a
1 parent
1ba13a5d
Add limited support for the etrax ethernet controller.
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@4429 c046a42c-6fe2-441c-8c8c-71466251a162
Showing
1 changed file
with
453 additions
and
0 deletions
hw/etraxfs_eth.c
0 → 100644
1 | +/* | |
2 | + * QEMU ETRAX Ethernet Controller. | |
3 | + * | |
4 | + * Copyright (c) 2008 Edgar E. Iglesias, Axis Communications AB. | |
5 | + * | |
6 | + * Permission is hereby granted, free of charge, to any person obtaining a copy | |
7 | + * of this software and associated documentation files (the "Software"), to deal | |
8 | + * in the Software without restriction, including without limitation the rights | |
9 | + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
10 | + * copies of the Software, and to permit persons to whom the Software is | |
11 | + * furnished to do so, subject to the following conditions: | |
12 | + * | |
13 | + * The above copyright notice and this permission notice shall be included in | |
14 | + * all copies or substantial portions of the Software. | |
15 | + * | |
16 | + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
17 | + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
18 | + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
19 | + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
20 | + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
21 | + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
22 | + * THE SOFTWARE. | |
23 | + */ | |
24 | + | |
25 | +#include <stdio.h> | |
26 | +#include "hw.h" | |
27 | +#include "net.h" | |
28 | + | |
29 | +#include "etraxfs_dma.h" | |
30 | + | |
31 | +#define D(x) | |
32 | + | |
33 | +#define R_STAT 0x2c | |
34 | +#define RW_MGM_CTRL 0x28 | |
35 | +#define FS_ETH_MAX_REGS 0x5c | |
36 | + | |
37 | + | |
38 | + | |
39 | +struct qemu_phy | |
40 | +{ | |
41 | + uint32_t regs[32]; | |
42 | + | |
43 | + unsigned int (*read)(struct qemu_phy *phy, unsigned int req); | |
44 | + void (*write)(struct qemu_phy *phy, unsigned int req, unsigned int data); | |
45 | +}; | |
46 | + | |
47 | +static unsigned int tdk_read(struct qemu_phy *phy, unsigned int req) | |
48 | +{ | |
49 | + int regnum; | |
50 | + unsigned r = 0; | |
51 | + | |
52 | + regnum = req & 0x1f; | |
53 | + | |
54 | + switch (regnum) { | |
55 | + case 1: | |
56 | + /* MR1. */ | |
57 | + /* Speeds and modes. */ | |
58 | + r |= (1 << 13) | (1 << 14); | |
59 | + r |= (1 << 11) | (1 << 12); | |
60 | + r |= (1 << 5); /* Autoneg complete. */ | |
61 | + r |= (1 << 3); /* Autoneg able. */ | |
62 | + r |= (1 << 2); /* Link. */ | |
63 | + break; | |
64 | + default: | |
65 | + r = phy->regs[regnum]; | |
66 | + break; | |
67 | + } | |
68 | + D(printf("%s %x = reg[%d]\n", __func__, r, regnum)); | |
69 | + return r; | |
70 | +} | |
71 | + | |
72 | +static void | |
73 | +tdk_write(struct qemu_phy *phy, unsigned int req, unsigned int data) | |
74 | +{ | |
75 | + int regnum; | |
76 | + | |
77 | + regnum = req & 0x1f; | |
78 | + D(printf("%s reg[%d] = %x\n", __func__, regnum, data)); | |
79 | + switch (regnum) { | |
80 | + default: | |
81 | + phy->regs[regnum] = data; | |
82 | + break; | |
83 | + } | |
84 | +} | |
85 | + | |
86 | +static void | |
87 | +tdk_init(struct qemu_phy *phy) | |
88 | +{ | |
89 | + phy->read = tdk_read; | |
90 | + phy->write = tdk_write; | |
91 | +} | |
92 | + | |
93 | +struct qemu_mdio | |
94 | +{ | |
95 | + /* bus. */ | |
96 | + int mdc; | |
97 | + int mdio; | |
98 | + | |
99 | + /* decoder. */ | |
100 | + enum { | |
101 | + PREAMBLE, | |
102 | + SOF, | |
103 | + OPC, | |
104 | + ADDR, | |
105 | + REQ, | |
106 | + TURNAROUND, | |
107 | + DATA | |
108 | + } state; | |
109 | + unsigned int drive; | |
110 | + | |
111 | + unsigned int cnt; | |
112 | + unsigned int addr; | |
113 | + unsigned int opc; | |
114 | + unsigned int req; | |
115 | + unsigned int data; | |
116 | + | |
117 | + struct qemu_phy *devs[32]; | |
118 | +}; | |
119 | + | |
120 | +static void | |
121 | +mdio_attach(struct qemu_mdio *bus, struct qemu_phy *phy, unsigned int addr) | |
122 | +{ | |
123 | + bus->devs[addr & 0x1f] = phy; | |
124 | +} | |
125 | + | |
126 | +static void | |
127 | +mdio_detach(struct qemu_mdio *bus, struct qemu_phy *phy, unsigned int addr) | |
128 | +{ | |
129 | + bus->devs[addr & 0x1f] = NULL; | |
130 | +} | |
131 | + | |
132 | +static void mdio_read_req(struct qemu_mdio *bus) | |
133 | +{ | |
134 | + struct qemu_phy *phy; | |
135 | + | |
136 | + phy = bus->devs[bus->addr]; | |
137 | + if (phy && phy->read) | |
138 | + bus->data = phy->read(phy, bus->req); | |
139 | + else | |
140 | + bus->data = 0xffff; | |
141 | +} | |
142 | + | |
143 | +static void mdio_write_req(struct qemu_mdio *bus) | |
144 | +{ | |
145 | + struct qemu_phy *phy; | |
146 | + | |
147 | + phy = bus->devs[bus->addr]; | |
148 | + if (phy && phy->write) | |
149 | + phy->write(phy, bus->req, bus->data); | |
150 | +} | |
151 | + | |
152 | +static void mdio_cycle(struct qemu_mdio *bus) | |
153 | +{ | |
154 | + bus->cnt++; | |
155 | + | |
156 | + D(printf("mdc=%d mdio=%d state=%d cnt=%d drv=%d\n", | |
157 | + bus->mdc, bus->mdio, bus->state, bus->cnt, bus->drive)); | |
158 | +#if 0 | |
159 | + if (bus->mdc) | |
160 | + printf("%d", bus->mdio); | |
161 | +#endif | |
162 | + switch (bus->state) | |
163 | + { | |
164 | + case PREAMBLE: | |
165 | + if (bus->mdc) { | |
166 | + if (bus->cnt >= (32 * 2) && !bus->mdio) { | |
167 | + bus->cnt = 0; | |
168 | + bus->state = SOF; | |
169 | + bus->data = 0; | |
170 | + } | |
171 | + } | |
172 | + break; | |
173 | + case SOF: | |
174 | + if (bus->mdc) { | |
175 | + if (bus->mdio != 1) | |
176 | + printf("WARNING: no SOF\n"); | |
177 | + if (bus->cnt == 1*2) { | |
178 | + bus->cnt = 0; | |
179 | + bus->opc = 0; | |
180 | + bus->state = OPC; | |
181 | + } | |
182 | + } | |
183 | + break; | |
184 | + case OPC: | |
185 | + if (bus->mdc) { | |
186 | + bus->opc <<= 1; | |
187 | + bus->opc |= bus->mdio & 1; | |
188 | + if (bus->cnt == 2*2) { | |
189 | + bus->cnt = 0; | |
190 | + bus->addr = 0; | |
191 | + bus->state = ADDR; | |
192 | + } | |
193 | + } | |
194 | + break; | |
195 | + case ADDR: | |
196 | + if (bus->mdc) { | |
197 | + bus->addr <<= 1; | |
198 | + bus->addr |= bus->mdio & 1; | |
199 | + | |
200 | + if (bus->cnt == 5*2) { | |
201 | + bus->cnt = 0; | |
202 | + bus->req = 0; | |
203 | + bus->state = REQ; | |
204 | + } | |
205 | + } | |
206 | + break; | |
207 | + case REQ: | |
208 | + if (bus->mdc) { | |
209 | + bus->req <<= 1; | |
210 | + bus->req |= bus->mdio & 1; | |
211 | + if (bus->cnt == 5*2) { | |
212 | + bus->cnt = 0; | |
213 | + bus->state = TURNAROUND; | |
214 | + } | |
215 | + } | |
216 | + break; | |
217 | + case TURNAROUND: | |
218 | + if (bus->mdc && bus->cnt == 2*2) { | |
219 | + bus->mdio = 0; | |
220 | + bus->cnt = 0; | |
221 | + | |
222 | + if (bus->opc == 2) { | |
223 | + bus->drive = 1; | |
224 | + mdio_read_req(bus); | |
225 | + bus->mdio = bus->data & 1; | |
226 | + } | |
227 | + bus->state = DATA; | |
228 | + } | |
229 | + break; | |
230 | + case DATA: | |
231 | + if (!bus->mdc) { | |
232 | + if (bus->drive) { | |
233 | + bus->mdio = bus->data & 1; | |
234 | + bus->data >>= 1; | |
235 | + } | |
236 | + } else { | |
237 | + if (!bus->drive) { | |
238 | + bus->data <<= 1; | |
239 | + bus->data |= bus->mdio; | |
240 | + } | |
241 | + if (bus->cnt == 16 * 2) { | |
242 | + bus->cnt = 0; | |
243 | + bus->state = PREAMBLE; | |
244 | + mdio_write_req(bus); | |
245 | + } | |
246 | + } | |
247 | + break; | |
248 | + default: | |
249 | + break; | |
250 | + } | |
251 | +} | |
252 | + | |
253 | + | |
254 | +struct fs_eth | |
255 | +{ | |
256 | + CPUState *env; | |
257 | + qemu_irq *irq; | |
258 | + target_phys_addr_t base; | |
259 | + VLANClientState *vc; | |
260 | + uint8_t macaddr[6]; | |
261 | + int ethregs; | |
262 | + | |
263 | + uint32_t regs[FS_ETH_MAX_REGS]; | |
264 | + | |
265 | + unsigned char rx_fifo[1536]; | |
266 | + int rx_fifo_len; | |
267 | + int rx_fifo_pos; | |
268 | + | |
269 | + struct etraxfs_dma_client *dma_out; | |
270 | + struct etraxfs_dma_client *dma_in; | |
271 | + | |
272 | + /* MDIO bus. */ | |
273 | + struct qemu_mdio mdio_bus; | |
274 | + /* PHY. */ | |
275 | + struct qemu_phy phy; | |
276 | +}; | |
277 | + | |
278 | +static uint32_t eth_rinvalid (void *opaque, target_phys_addr_t addr) | |
279 | +{ | |
280 | + struct fs_eth *eth = opaque; | |
281 | + CPUState *env = eth->env; | |
282 | + cpu_abort(env, "Unsupported short access. reg=%x pc=%x.\n", | |
283 | + addr, env->pc); | |
284 | + return 0; | |
285 | +} | |
286 | + | |
287 | +static uint32_t eth_readl (void *opaque, target_phys_addr_t addr) | |
288 | +{ | |
289 | + struct fs_eth *eth = opaque; | |
290 | + D(CPUState *env = eth->env); | |
291 | + uint32_t r = 0; | |
292 | + | |
293 | + /* Make addr relative to this instances base. */ | |
294 | + addr -= eth->base; | |
295 | + switch (addr) { | |
296 | + case R_STAT: | |
297 | + /* Attach an MDIO/PHY abstraction. */ | |
298 | + r = eth->mdio_bus.mdio & 1; | |
299 | + break; | |
300 | + default: | |
301 | + r = eth->regs[addr]; | |
302 | + D(printf ("%s %x p=%x\n", __func__, addr, env->pc)); | |
303 | + break; | |
304 | + } | |
305 | + return r; | |
306 | +} | |
307 | + | |
308 | +static void | |
309 | +eth_winvalid (void *opaque, target_phys_addr_t addr, uint32_t value) | |
310 | +{ | |
311 | + struct fs_eth *eth = opaque; | |
312 | + CPUState *env = eth->env; | |
313 | + cpu_abort(env, "Unsupported short access. reg=%x pc=%x.\n", | |
314 | + addr, env->pc); | |
315 | +} | |
316 | + | |
317 | +static void | |
318 | +eth_writel (void *opaque, target_phys_addr_t addr, uint32_t value) | |
319 | +{ | |
320 | + struct fs_eth *eth = opaque; | |
321 | + CPUState *env = eth->env; | |
322 | + | |
323 | + /* Make addr relative to this instances base. */ | |
324 | + addr -= eth->base; | |
325 | + switch (addr) | |
326 | + { | |
327 | + case RW_MGM_CTRL: | |
328 | + /* Attach an MDIO/PHY abstraction. */ | |
329 | + if (value & 2) | |
330 | + eth->mdio_bus.mdio = value & 1; | |
331 | + if (eth->mdio_bus.mdc != (value & 4)) | |
332 | + mdio_cycle(ð->mdio_bus); | |
333 | + eth->mdio_bus.mdc = !!(value & 4); | |
334 | + break; | |
335 | + | |
336 | + default: | |
337 | + printf ("%s %x %x pc=%x\n", | |
338 | + __func__, addr, value, env->pc); | |
339 | + break; | |
340 | + } | |
341 | +} | |
342 | + | |
343 | +static int eth_can_receive(void *opaque) | |
344 | +{ | |
345 | + struct fs_eth *eth = opaque; | |
346 | + int r; | |
347 | + | |
348 | + r = eth->rx_fifo_len == 0; | |
349 | + if (!r) { | |
350 | + /* TODO: signal fifo overrun. */ | |
351 | + printf("PACKET LOSS!\n"); | |
352 | + } | |
353 | + return r; | |
354 | +} | |
355 | + | |
356 | +static void eth_receive(void *opaque, const uint8_t *buf, int size) | |
357 | +{ | |
358 | + struct fs_eth *eth = opaque; | |
359 | + if (size > sizeof(eth->rx_fifo)) { | |
360 | + /* TODO: signal error. */ | |
361 | + } else { | |
362 | + memcpy(eth->rx_fifo, buf, size); | |
363 | + /* +4, HW passes the CRC to sw. */ | |
364 | + eth->rx_fifo_len = size + 4; | |
365 | + eth->rx_fifo_pos = 0; | |
366 | + } | |
367 | +} | |
368 | + | |
369 | +static void eth_rx_pull(void *opaque) | |
370 | +{ | |
371 | + struct fs_eth *eth = opaque; | |
372 | + int len; | |
373 | + if (eth->rx_fifo_len) { | |
374 | + D(printf("%s %d\n", __func__, eth->rx_fifo_len)); | |
375 | +#if 0 | |
376 | + { | |
377 | + int i; | |
378 | + for (i = 0; i < 32; i++) | |
379 | + printf("%2.2x", eth->rx_fifo[i]); | |
380 | + printf("\n"); | |
381 | + } | |
382 | +#endif | |
383 | + len = etraxfs_dmac_input(eth->dma_in, | |
384 | + eth->rx_fifo + eth->rx_fifo_pos, | |
385 | + eth->rx_fifo_len, 1); | |
386 | + eth->rx_fifo_len -= len; | |
387 | + eth->rx_fifo_pos += len; | |
388 | + } | |
389 | +} | |
390 | + | |
391 | +static int eth_tx_push(void *opaque, unsigned char *buf, int len) | |
392 | +{ | |
393 | + struct fs_eth *eth = opaque; | |
394 | + | |
395 | + D(printf("%s buf=%p len=%d\n", __func__, buf, len)); | |
396 | + qemu_send_packet(eth->vc, buf, len); | |
397 | + return len; | |
398 | +} | |
399 | + | |
400 | +static CPUReadMemoryFunc *eth_read[] = { | |
401 | + ð_rinvalid, | |
402 | + ð_rinvalid, | |
403 | + ð_readl, | |
404 | +}; | |
405 | + | |
406 | +static CPUWriteMemoryFunc *eth_write[] = { | |
407 | + ð_winvalid, | |
408 | + ð_winvalid, | |
409 | + ð_writel, | |
410 | +}; | |
411 | + | |
412 | +void *etraxfs_eth_init(NICInfo *nd, CPUState *env, | |
413 | + qemu_irq *irq, target_phys_addr_t base) | |
414 | +{ | |
415 | + struct etraxfs_dma_client *dma = NULL; | |
416 | + struct fs_eth *eth = NULL; | |
417 | + | |
418 | + dma = qemu_mallocz(sizeof *dma * 2); | |
419 | + if (!dma) | |
420 | + return NULL; | |
421 | + | |
422 | + eth = qemu_mallocz(sizeof *eth); | |
423 | + if (!eth) | |
424 | + goto err; | |
425 | + | |
426 | + dma[0].client.push = eth_tx_push; | |
427 | + dma[0].client.opaque = eth; | |
428 | + dma[1].client.opaque = eth; | |
429 | + dma[1].client.pull = eth_rx_pull; | |
430 | + | |
431 | + eth->env = env; | |
432 | + eth->base = base; | |
433 | + eth->irq = irq; | |
434 | + eth->dma_out = dma; | |
435 | + eth->dma_in = dma + 1; | |
436 | + memcpy(eth->macaddr, nd->macaddr, 6); | |
437 | + | |
438 | + /* Connect the phy. */ | |
439 | + tdk_init(ð->phy); | |
440 | + mdio_attach(ð->mdio_bus, ð->phy, 0x1); | |
441 | + | |
442 | + eth->ethregs = cpu_register_io_memory(0, eth_read, eth_write, eth); | |
443 | + cpu_register_physical_memory (base, 0x5c, eth->ethregs); | |
444 | + | |
445 | + eth->vc = qemu_new_vlan_client(nd->vlan, | |
446 | + eth_receive, eth_can_receive, eth); | |
447 | + | |
448 | + return dma; | |
449 | + err: | |
450 | + qemu_free(eth); | |
451 | + qemu_free(dma); | |
452 | + return NULL; | |
453 | +} | ... | ... |