source: mainline/uspace/srv/hw/netif/dp8390/dp8390.c@ 43b4314

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 43b4314 was 43b4314, checked in by Martin Decky <martin@…>, 14 years ago

refactor NE2000 up/down operations, do not configure the card prematurelly (during probing)

  • Property mode set to 100644
File size: 16.4 KB
Line 
1/*
2 * Copyright (c) 2009 Lukas Mejdrech
3 * Copyright (c) 2011 Martin Decky
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * - Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * - The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30/*
31 * This code is based upon the NE2000 driver for MINIX,
32 * distributed according to a BSD-style license.
33 *
34 * Copyright (c) 1987, 1997, 2006 Vrije Universiteit
35 * Copyright (c) 1992, 1994 Philip Homburg
36 * Copyright (c) 1996 G. Falzoni
37 *
38 */
39
40/** @addtogroup ne2000
41 * @{
42 */
43
44/** @file
45 *
46 * NE2000 (based on DP8390) network interface core implementation.
47 * Only the basic NE2000 PIO (ISA) interface is supported, remote
48 * DMA is completely absent from this code for simplicity.
49 *
50 */
51
52#include <assert.h>
53#include <byteorder.h>
54#include <errno.h>
55#include <libarch/ddi.h>
56#include <netif_skel.h>
57#include <net/packet.h>
58#include <nil_interface.h>
59#include <packet_client.h>
60#include "dp8390.h"
61
62/** Page size */
63#define DP_PAGE 256
64
65/** 6 * DP_PAGE >= 1514 bytes */
66#define SQ_PAGES 6
67
68/* NE2000 implementation. */
69
70/** NE2000 Data Register */
71#define NE2K_DATA 0x0010
72
73/** NE2000 Reset register */
74#define NE2K_RESET 0x001f
75
76/** NE2000 data start */
77#define NE2K_START 0x4000
78
79/** NE2000 data size */
80#define NE2K_SIZE 0x4000
81
82/** NE2000 retry count */
83#define NE2K_RETRY 0x1000
84
85/** NE2000 error messages rate limiting */
86#define NE2K_ERL 10
87
88/** Minimum Ethernet packet size in bytes */
89#define ETH_MIN_PACK_SIZE 60
90
91/** Maximum Ethernet packet size in bytes */
92#define ETH_MAX_PACK_SIZE_TAGGED 1518
93
94/** Type definition of the receive header
95 *
96 */
97typedef struct {
98 /** Copy of RSR */
99 uint8_t status;
100
101 /** Pointer to next packet */
102 uint8_t next;
103
104 /** Receive Byte Count Low */
105 uint8_t rbcl;
106
107 /** Receive Byte Count High */
108 uint8_t rbch;
109} recv_header_t;
110
111/** Read a memory block word by word.
112 *
113 * @param[in] port Source address.
114 * @param[out] buf Destination buffer.
115 * @param[in] size Memory block size in bytes.
116 *
117 */
118static void pio_read_buf_16(void *port, void *buf, size_t size)
119{
120 size_t i;
121
122 for (i = 0; (i << 1) < size; i++)
123 *((uint16_t *) buf + i) = pio_read_16((ioport16_t *) (port));
124}
125
126/** Write a memory block word by word.
127 *
128 * @param[in] port Destination address.
129 * @param[in] buf Source buffer.
130 * @param[in] size Memory block size in bytes.
131 *
132 */
133static void pio_write_buf_16(void *port, void *buf, size_t size)
134{
135 size_t i;
136
137 for (i = 0; (i << 1) < size; i++)
138 pio_write_16((ioport16_t *) port, *((uint16_t *) buf + i));
139}
140
141static void ne2k_download(ne2k_t *ne2k, void *buf, size_t addr, size_t size)
142{
143 size_t esize = size & ~1;
144
145 pio_write_8(ne2k->port + DP_RBCR0, esize & 0xff);
146 pio_write_8(ne2k->port + DP_RBCR1, (esize >> 8) & 0xff);
147 pio_write_8(ne2k->port + DP_RSAR0, addr & 0xff);
148 pio_write_8(ne2k->port + DP_RSAR1, (addr >> 8) & 0xff);
149 pio_write_8(ne2k->port + DP_CR, CR_DM_RR | CR_PS_P0 | CR_STA);
150
151 if (esize != 0) {
152 pio_read_buf_16(ne2k->data_port, buf, esize);
153 size -= esize;
154 buf += esize;
155 }
156
157 if (size) {
158 assert(size == 1);
159
160 uint16_t word = pio_read_16(ne2k->data_port);
161 memcpy(buf, &word, 1);
162 }
163}
164
165static void ne2k_upload(ne2k_t *ne2k, void *buf, size_t addr, size_t size)
166{
167 size_t esize = size & ~1;
168
169 pio_write_8(ne2k->port + DP_RBCR0, esize & 0xff);
170 pio_write_8(ne2k->port + DP_RBCR1, (esize >> 8) & 0xff);
171 pio_write_8(ne2k->port + DP_RSAR0, addr & 0xff);
172 pio_write_8(ne2k->port + DP_RSAR1, (addr >> 8) & 0xff);
173 pio_write_8(ne2k->port + DP_CR, CR_DM_RW | CR_PS_P0 | CR_STA);
174
175 if (esize != 0) {
176 pio_write_buf_16(ne2k->data_port, buf, esize);
177 size -= esize;
178 buf += esize;
179 }
180
181 if (size) {
182 assert(size == 1);
183
184 uint16_t word = 0;
185
186 memcpy(&word, buf, 1);
187 pio_write_16(ne2k->data_port, word);
188 }
189}
190
191static void ne2k_init(ne2k_t *ne2k)
192{
193 unsigned int i;
194
195 /* Reset the ethernet card */
196 uint8_t val = pio_read_8(ne2k->port + NE2K_RESET);
197 usleep(2000);
198 pio_write_8(ne2k->port + NE2K_RESET, val);
199 usleep(2000);
200
201 /* Reset the DP8390 */
202 pio_write_8(ne2k->port + DP_CR, CR_STP | CR_DM_ABORT);
203 for (i = 0; i < NE2K_RETRY; i++) {
204 if (pio_read_8(ne2k->port + DP_ISR) != 0)
205 break;
206 }
207}
208
209/** Probe and initialize the network interface.
210 *
211 * @param[in,out] ne2k Network interface structure.
212 * @param[in] port Device address.
213 * @param[in] irq Device interrupt vector.
214 *
215 * @return EOK on success.
216 * @return EXDEV if the network interface was not recognized.
217 *
218 */
219int ne2k_probe(ne2k_t *ne2k, void *port, int irq)
220{
221 unsigned int i;
222
223 /* General initialization */
224 ne2k->port = port;
225 ne2k->data_port = ne2k->port + NE2K_DATA;
226 ne2k->irq = irq;
227 ne2k->probed = false;
228 ne2k->up = false;
229
230 ne2k_init(ne2k);
231
232 /* Check if the DP8390 is really there */
233 uint8_t val = pio_read_8(ne2k->port + DP_CR);
234 if ((val & (CR_STP | CR_DM_ABORT)) != (CR_STP | CR_DM_ABORT))
235 return EXDEV;
236
237 /* Disable the receiver and init TCR and DCR */
238 pio_write_8(ne2k->port + DP_RCR, RCR_MON);
239 pio_write_8(ne2k->port + DP_TCR, TCR_NORMAL);
240 pio_write_8(ne2k->port + DP_DCR, DCR_WORDWIDE | DCR_8BYTES | DCR_BMS);
241
242 /* Setup a transfer to get the MAC address */
243 pio_write_8(ne2k->port + DP_RBCR0, ETH_ADDR << 1);
244 pio_write_8(ne2k->port + DP_RBCR1, 0);
245 pio_write_8(ne2k->port + DP_RSAR0, 0);
246 pio_write_8(ne2k->port + DP_RSAR1, 0);
247 pio_write_8(ne2k->port + DP_CR, CR_DM_RR | CR_PS_P0 | CR_STA);
248
249 for (i = 0; i < ETH_ADDR; i++)
250 ne2k->mac[i] = pio_read_16(ne2k->data_port);
251
252 ne2k->probed = true;
253 return EOK;
254}
255
256/** Start the network interface.
257 *
258 * @param[in,out] ne2k Network interface structure.
259 *
260 * @return EOK on success.
261 * @return EXDEV if the network interface is disabled.
262 *
263 */
264int ne2k_up(ne2k_t *ne2k)
265{
266 if (!ne2k->probed)
267 return EXDEV;
268
269 ne2k_init(ne2k);
270
271 /*
272 * Setup send queue. Use the first
273 * SQ_PAGES of NE2000 memory for the send
274 * buffer.
275 */
276 ne2k->sq.dirty = false;
277 ne2k->sq.page = NE2K_START / DP_PAGE;
278 fibril_mutex_initialize(&ne2k->sq_mutex);
279 fibril_condvar_initialize(&ne2k->sq_cv);
280
281 /*
282 * Setup receive ring buffer. Use all the rest
283 * of the NE2000 memory (except the first SQ_PAGES
284 * reserved for the send buffer) for the receive
285 * ring buffer.
286 */
287 ne2k->start_page = ne2k->sq.page + SQ_PAGES;
288 ne2k->stop_page = ne2k->sq.page + NE2K_SIZE / DP_PAGE;
289
290 /*
291 * Initialization of the DP8390 following the mandatory procedure
292 * in reference manual ("DP8390D/NS32490D NIC Network Interface
293 * Controller", National Semiconductor, July 1995, Page 29).
294 */
295
296 /* Step 1: */
297 pio_write_8(ne2k->port + DP_CR, CR_PS_P0 | CR_STP | CR_DM_ABORT);
298
299 /* Step 2: */
300 pio_write_8(ne2k->port + DP_DCR, DCR_WORDWIDE | DCR_8BYTES | DCR_BMS);
301
302 /* Step 3: */
303 pio_write_8(ne2k->port + DP_RBCR0, 0);
304 pio_write_8(ne2k->port + DP_RBCR1, 0);
305
306 /* Step 4: */
307 pio_write_8(ne2k->port + DP_RCR, RCR_AB);
308
309 /* Step 5: */
310 pio_write_8(ne2k->port + DP_TCR, TCR_INTERNAL);
311
312 /* Step 6: */
313 pio_write_8(ne2k->port + DP_BNRY, ne2k->start_page);
314 pio_write_8(ne2k->port + DP_PSTART, ne2k->start_page);
315 pio_write_8(ne2k->port + DP_PSTOP, ne2k->stop_page);
316
317 /* Step 7: */
318 pio_write_8(ne2k->port + DP_ISR, 0xff);
319
320 /* Step 8: */
321 pio_write_8(ne2k->port + DP_IMR,
322 IMR_PRXE | IMR_PTXE | IMR_RXEE | IMR_TXEE | IMR_OVWE | IMR_CNTE);
323
324 /* Step 9: */
325 pio_write_8(ne2k->port + DP_CR, CR_PS_P1 | CR_DM_ABORT | CR_STP);
326
327 pio_write_8(ne2k->port + DP_PAR0, ne2k->mac[0]);
328 pio_write_8(ne2k->port + DP_PAR1, ne2k->mac[1]);
329 pio_write_8(ne2k->port + DP_PAR2, ne2k->mac[2]);
330 pio_write_8(ne2k->port + DP_PAR3, ne2k->mac[3]);
331 pio_write_8(ne2k->port + DP_PAR4, ne2k->mac[4]);
332 pio_write_8(ne2k->port + DP_PAR5, ne2k->mac[5]);
333
334 pio_write_8(ne2k->port + DP_MAR0, 0xff);
335 pio_write_8(ne2k->port + DP_MAR1, 0xff);
336 pio_write_8(ne2k->port + DP_MAR2, 0xff);
337 pio_write_8(ne2k->port + DP_MAR3, 0xff);
338 pio_write_8(ne2k->port + DP_MAR4, 0xff);
339 pio_write_8(ne2k->port + DP_MAR5, 0xff);
340 pio_write_8(ne2k->port + DP_MAR6, 0xff);
341 pio_write_8(ne2k->port + DP_MAR7, 0xff);
342
343 pio_write_8(ne2k->port + DP_CURR, ne2k->start_page + 1);
344
345 /* Step 10: */
346 pio_write_8(ne2k->port + DP_CR, CR_DM_ABORT | CR_STA);
347
348 /* Step 11: */
349 pio_write_8(ne2k->port + DP_TCR, TCR_NORMAL);
350
351 /* Reset counters by reading */
352 pio_read_8(ne2k->port + DP_CNTR0);
353 pio_read_8(ne2k->port + DP_CNTR1);
354 pio_read_8(ne2k->port + DP_CNTR2);
355
356 /* Finish the initialization */
357 ne2k->up = true;
358 return EOK;
359}
360
361/** Stop the network interface.
362 *
363 * @param[in,out] ne2k Network interface structure.
364 *
365 */
366void ne2k_down(ne2k_t *ne2k)
367{
368 if ((ne2k->probed) && (ne2k->up)) {
369 pio_write_8(ne2k->port + DP_CR, CR_STP | CR_DM_ABORT);
370 ne2k_init(ne2k);
371 ne2k->up = false;
372 }
373}
374
375/** Send a frame.
376 *
377 * @param[in,out] ne2k Network interface structure.
378 * @param[in] packet Frame to be sent.
379 *
380 */
381void ne2k_send(ne2k_t *ne2k, packet_t *packet)
382{
383 assert(ne2k->probed);
384 assert(ne2k->up);
385
386 fibril_mutex_lock(&ne2k->sq_mutex);
387
388 while (ne2k->sq.dirty)
389 fibril_condvar_wait(&ne2k->sq_cv, &ne2k->sq_mutex);
390
391 void *buf = packet_get_data(packet);
392 size_t size = packet_get_data_length(packet);
393
394 if ((size < ETH_MIN_PACK_SIZE) || (size > ETH_MAX_PACK_SIZE_TAGGED)) {
395 fprintf(stderr, "%s: Frame dropped (invalid size %zu bytes)\n",
396 NAME, size);
397 return;
398 }
399
400 /* Upload the frame to the ethernet card */
401 ne2k_upload(ne2k, buf, ne2k->sq.page * DP_PAGE, size);
402 ne2k->sq.dirty = true;
403 ne2k->sq.size = size;
404
405 /* Initialize the transfer */
406 pio_write_8(ne2k->port + DP_TPSR, ne2k->sq.page);
407 pio_write_8(ne2k->port + DP_TBCR0, size & 0xff);
408 pio_write_8(ne2k->port + DP_TBCR1, (size >> 8) & 0xff);
409 pio_write_8(ne2k->port + DP_CR, CR_TXP | CR_STA);
410
411 fibril_mutex_unlock(&ne2k->sq_mutex);
412}
413
414static void ne2k_reset(ne2k_t *ne2k)
415{
416 unsigned int i;
417
418 /* Stop the chip */
419 pio_write_8(ne2k->port + DP_CR, CR_STP | CR_DM_ABORT);
420 pio_write_8(ne2k->port + DP_RBCR0, 0);
421 pio_write_8(ne2k->port + DP_RBCR1, 0);
422
423 for (i = 0; i < NE2K_RETRY; i++) {
424 if ((pio_read_8(ne2k->port + DP_ISR) & ISR_RST) != 0)
425 break;
426 }
427
428 pio_write_8(ne2k->port + DP_TCR, TCR_1EXTERNAL | TCR_OFST);
429 pio_write_8(ne2k->port + DP_CR, CR_STA | CR_DM_ABORT);
430 pio_write_8(ne2k->port + DP_TCR, TCR_NORMAL);
431
432 /* Acknowledge the ISR_RDC (remote DMA) interrupt */
433 for (i = 0; i < NE2K_RETRY; i++) {
434 if ((pio_read_8(ne2k->port + DP_ISR) & ISR_RDC) != 0)
435 break;
436 }
437
438 uint8_t val = pio_read_8(ne2k->port + DP_ISR);
439 pio_write_8(ne2k->port + DP_ISR, val & ~ISR_RDC);
440
441 /*
442 * Reset the transmit ring. If we were transmitting a frame,
443 * we pretend that the packet is processed. Higher layers will
444 * retransmit if the packet wasn't actually sent.
445 */
446 fibril_mutex_lock(&ne2k->sq_mutex);
447 ne2k->sq.dirty = false;
448 fibril_mutex_unlock(&ne2k->sq_mutex);
449}
450
451static void ne2k_receive_frame(ne2k_t *ne2k, uint8_t page, size_t length,
452 int nil_phone, device_id_t device_id)
453{
454 packet_t *packet = netif_packet_get_1(length);
455 if (!packet)
456 return;
457
458 void *buf = packet_suffix(packet, length);
459 bzero(buf, length);
460 uint8_t last = page + length / DP_PAGE;
461
462 if (last >= ne2k->stop_page) {
463 size_t left = (ne2k->stop_page - page) * DP_PAGE
464 - sizeof(recv_header_t);
465
466 ne2k_download(ne2k, buf, page * DP_PAGE + sizeof(recv_header_t),
467 left);
468 ne2k_download(ne2k, buf + left, ne2k->start_page * DP_PAGE,
469 length - left);
470 } else
471 ne2k_download(ne2k, buf, page * DP_PAGE + sizeof(recv_header_t),
472 length);
473
474 ne2k->stats.receive_packets++;
475 nil_received_msg(nil_phone, device_id, packet, SERVICE_NONE);
476}
477
478static void ne2k_receive(ne2k_t *ne2k, int nil_phone, device_id_t device_id)
479{
480 while (true) {
481 uint8_t boundary = pio_read_8(ne2k->port + DP_BNRY) + 1;
482
483 if (boundary == ne2k->stop_page)
484 boundary = ne2k->start_page;
485
486 pio_write_8(ne2k->port + DP_CR, CR_PS_P1 | CR_STA);
487 uint8_t current = pio_read_8(ne2k->port + DP_CURR);
488 pio_write_8(ne2k->port + DP_CR, CR_PS_P0 | CR_STA);
489
490 if (current == boundary)
491 /* No more frames to process */
492 break;
493
494 recv_header_t header;
495 size_t size = sizeof(header);
496 size_t offset = boundary * DP_PAGE;
497
498 /* Get the frame header */
499 pio_write_8(ne2k->port + DP_RBCR0, size & 0xff);
500 pio_write_8(ne2k->port + DP_RBCR1, (size >> 8) & 0xff);
501 pio_write_8(ne2k->port + DP_RSAR0, offset & 0xff);
502 pio_write_8(ne2k->port + DP_RSAR1, (offset >> 8) & 0xff);
503 pio_write_8(ne2k->port + DP_CR, CR_DM_RR | CR_PS_P0 | CR_STA);
504
505 pio_read_buf_16(ne2k->data_port, (void *) &header, size);
506
507 size_t length =
508 (((size_t) header.rbcl) | (((size_t) header.rbch) << 8)) - size;
509 uint8_t next = header.next;
510
511 if ((length < ETH_MIN_PACK_SIZE)
512 || (length > ETH_MAX_PACK_SIZE_TAGGED)) {
513 fprintf(stderr, "%s: Rant frame (%zu bytes)\n", NAME, length);
514 next = current;
515 } else if ((header.next < ne2k->start_page)
516 || (header.next > ne2k->stop_page)) {
517 fprintf(stderr, "%s: Malformed next frame %u\n", NAME,
518 header.next);
519 next = current;
520 } else if (header.status & RSR_FO) {
521 /*
522 * This is very serious, so we issue a warning and
523 * reset the buffers.
524 */
525 fprintf(stderr, "%s: FIFO overrun\n", NAME);
526 ne2k->overruns++;
527 next = current;
528 } else if ((header.status & RSR_PRX) && (ne2k->up))
529 ne2k_receive_frame(ne2k, boundary, length, nil_phone, device_id);
530
531 /*
532 * Update the boundary pointer
533 * to the value of the page
534 * prior to the next packet to
535 * be processed.
536 */
537 if (next == ne2k->start_page)
538 next = ne2k->stop_page - 1;
539 else
540 next--;
541
542 pio_write_8(ne2k->port + DP_BNRY, next);
543 }
544}
545
546void ne2k_interrupt(ne2k_t *ne2k, uint8_t isr, int nil_phone, device_id_t device_id)
547{
548 if (isr & (ISR_PTX | ISR_TXE)) {
549 if (isr & ISR_TXE)
550 ne2k->stats.send_errors++;
551 else {
552 uint8_t tsr = pio_read_8(ne2k->port + DP_TSR);
553
554 if (tsr & TSR_PTX)
555 ne2k->stats.send_packets++;
556
557 if (tsr & TSR_COL)
558 ne2k->stats.collisions++;
559
560 if (tsr & TSR_ABT)
561 ne2k->stats.send_aborted_errors++;
562
563 if (tsr & TSR_CRS)
564 ne2k->stats.send_carrier_errors++;
565
566 if (tsr & TSR_FU) {
567 ne2k->underruns++;
568 if (ne2k->underruns < NE2K_ERL)
569 fprintf(stderr, "%s: FIFO underrun\n", NAME);
570 }
571
572 if (tsr & TSR_CDH) {
573 ne2k->stats.send_heartbeat_errors++;
574 if (ne2k->stats.send_heartbeat_errors < NE2K_ERL)
575 fprintf(stderr, "%s: CD heartbeat failure\n", NAME);
576 }
577
578 if (tsr & TSR_OWC)
579 ne2k->stats.send_window_errors++;
580 }
581
582 fibril_mutex_lock(&ne2k->sq_mutex);
583
584 if (ne2k->sq.dirty) {
585 /* Prepare the buffer for next packet */
586 ne2k->sq.dirty = false;
587 ne2k->sq.size = 0;
588
589 /* Signal a next frame to be sent */
590 fibril_condvar_broadcast(&ne2k->sq_cv);
591 } else {
592 ne2k->misses++;
593 if (ne2k->misses < NE2K_ERL)
594 fprintf(stderr, "%s: Spurious PTX interrupt\n", NAME);
595 }
596
597 fibril_mutex_unlock(&ne2k->sq_mutex);
598 }
599
600 if (isr & ISR_PRX)
601 ne2k_receive(ne2k, nil_phone, device_id);
602
603 if (isr & ISR_RXE)
604 ne2k->stats.receive_errors++;
605
606 if (isr & ISR_CNT) {
607 ne2k->stats.receive_crc_errors +=
608 pio_read_8(ne2k->port + DP_CNTR0);
609 ne2k->stats.receive_frame_errors +=
610 pio_read_8(ne2k->port + DP_CNTR1);
611 ne2k->stats.receive_missed_errors +=
612 pio_read_8(ne2k->port + DP_CNTR2);
613 }
614
615 if (isr & ISR_RST) {
616 /*
617 * The chip is stopped, and all arrived
618 * frames are delivered.
619 */
620 ne2k_reset(ne2k);
621 }
622
623 /* Unmask interrupts to be processed in the next round */
624 pio_write_8(ne2k->port + DP_IMR,
625 IMR_PRXE | IMR_PTXE | IMR_RXEE | IMR_TXEE | IMR_OVWE | IMR_CNTE);
626}
627
628/** @}
629 */
Note: See TracBrowser for help on using the repository browser.