Changeset d7ff048 in mainline for uspace/drv/nic/ne2k/dp8390.c


Ignore:
Timestamp:
2011-10-08T13:08:53Z (13 years ago)
Author:
Maurizio Lombardi <m.lombardi85@…>
Branches:
lfn, master, serial, ticket/834-toolchain-update, topic/msim-upgrade, topic/simplify-dev-export
Children:
bf08ff0
Parents:
8367d1d (diff), 80099c19 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
Message:

Merge mainline changes

File:
1 moved

Legend:

Unmodified
Added
Removed
  • uspace/drv/nic/ne2k/dp8390.c

    r8367d1d rd7ff048  
    22 * Copyright (c) 2009 Lukas Mejdrech
    33 * Copyright (c) 2011 Martin Decky
     4 * Copyright (c) 2011 Radim Vansa
    45 * All rights reserved.
    56 *
     
    3839 */
    3940
    40 /** @addtogroup ne2000
    41  *  @{
    42  */
    43 
    44 /** @file
     41/**
     42 * @addtogroup drv_ne2k
     43 * @{
     44 */
     45
     46/**
     47 * @file
     48 * @brief NE2000 driver core
    4549 *
    4650 * NE2000 (based on DP8390) network interface core implementation.
     
    6569#define SQ_PAGES  6
    6670
    67 /* NE2000 implementation. */
    68 
    69 /** NE2000 Data Register */
    70 #define NE2K_DATA  0x0010
    71 
    72 /** NE2000 Reset register */
    73 #define NE2K_RESET  0x001f
    74 
    75 /** NE2000 data start */
    76 #define NE2K_START  0x4000
    77 
    78 /** NE2000 data size */
    79 #define NE2K_SIZE  0x4000
    80 
    81 /** NE2000 retry count */
    82 #define NE2K_RETRY  0x1000
    83 
    84 /** NE2000 error messages rate limiting */
    85 #define NE2K_ERL  10
    86 
    87 /** Minimum Ethernet packet size in bytes */
    88 #define ETH_MIN_PACK_SIZE  60
    89 
    90 /** Maximum Ethernet packet size in bytes */
    91 #define ETH_MAX_PACK_SIZE_TAGGED  1518
    92 
    9371/** Type definition of the receive header
    9472 *
     
    216194 *
    217195 */
    218 int ne2k_probe(ne2k_t *ne2k, void *port, int irq)
     196int ne2k_probe(ne2k_t *ne2k)
    219197{
    220198        unsigned int i;
    221        
    222         /* General initialization */
    223         ne2k->port = port;
    224         ne2k->data_port = ne2k->port + NE2K_DATA;
    225         ne2k->irq = irq;
    226         ne2k->probed = false;
    227         ne2k->up = false;
    228199       
    229200        ne2k_init(ne2k);
     
    247218       
    248219        for (i = 0; i < ETH_ADDR; i++)
    249                 ne2k->mac[i] = pio_read_16(ne2k->data_port);
    250        
    251         ne2k->probed = true;
     220                ne2k->mac.address[i] = pio_read_16(ne2k->data_port);
     221       
    252222        return EOK;
     223}
     224
     225void ne2k_set_physical_address(ne2k_t *ne2k, const nic_address_t *address)
     226{
     227        memcpy(&ne2k->mac, address, sizeof(nic_address_t));
     228       
     229        pio_write_8(ne2k->port + DP_CR, CR_PS_P0 | CR_DM_ABORT | CR_STP);
     230       
     231        pio_write_8(ne2k->port + DP_RBCR0, ETH_ADDR << 1);
     232        pio_write_8(ne2k->port + DP_RBCR1, 0);
     233        pio_write_8(ne2k->port + DP_RSAR0, 0);
     234        pio_write_8(ne2k->port + DP_RSAR1, 0);
     235        pio_write_8(ne2k->port + DP_CR, CR_DM_RW | CR_PS_P0 | CR_STA);
     236
     237        size_t i;
     238        for (i = 0; i < ETH_ADDR; i++)
     239                pio_write_16(ne2k->data_port, ne2k->mac.address[i]);
     240
     241        //pio_write_8(ne2k->port + DP_CR, CR_PS_P0 | CR_DM_ABORT | CR_STA);
    253242}
    254243
     
    304293       
    305294        /* Step 4: */
    306         pio_write_8(ne2k->port + DP_RCR, RCR_AB);
     295        pio_write_8(ne2k->port + DP_RCR, ne2k->receive_configuration);
    307296       
    308297        /* Step 5: */
     
    324313        pio_write_8(ne2k->port + DP_CR, CR_PS_P1 | CR_DM_ABORT | CR_STP);
    325314       
    326         pio_write_8(ne2k->port + DP_PAR0, ne2k->mac[0]);
    327         pio_write_8(ne2k->port + DP_PAR1, ne2k->mac[1]);
    328         pio_write_8(ne2k->port + DP_PAR2, ne2k->mac[2]);
    329         pio_write_8(ne2k->port + DP_PAR3, ne2k->mac[3]);
    330         pio_write_8(ne2k->port + DP_PAR4, ne2k->mac[4]);
    331         pio_write_8(ne2k->port + DP_PAR5, ne2k->mac[5]);
    332        
    333         pio_write_8(ne2k->port + DP_MAR0, 0xff);
    334         pio_write_8(ne2k->port + DP_MAR1, 0xff);
    335         pio_write_8(ne2k->port + DP_MAR2, 0xff);
    336         pio_write_8(ne2k->port + DP_MAR3, 0xff);
    337         pio_write_8(ne2k->port + DP_MAR4, 0xff);
    338         pio_write_8(ne2k->port + DP_MAR5, 0xff);
    339         pio_write_8(ne2k->port + DP_MAR6, 0xff);
    340         pio_write_8(ne2k->port + DP_MAR7, 0xff);
     315        pio_write_8(ne2k->port + DP_PAR0, ne2k->mac.address[0]);
     316        pio_write_8(ne2k->port + DP_PAR1, ne2k->mac.address[1]);
     317        pio_write_8(ne2k->port + DP_PAR2, ne2k->mac.address[2]);
     318        pio_write_8(ne2k->port + DP_PAR3, ne2k->mac.address[3]);
     319        pio_write_8(ne2k->port + DP_PAR4, ne2k->mac.address[4]);
     320        pio_write_8(ne2k->port + DP_PAR5, ne2k->mac.address[5]);
     321       
     322        pio_write_8(ne2k->port + DP_MAR0, 0);
     323        pio_write_8(ne2k->port + DP_MAR1, 0);
     324        pio_write_8(ne2k->port + DP_MAR2, 0);
     325        pio_write_8(ne2k->port + DP_MAR3, 0);
     326        pio_write_8(ne2k->port + DP_MAR4, 0);
     327        pio_write_8(ne2k->port + DP_MAR5, 0);
     328        pio_write_8(ne2k->port + DP_MAR6, 0);
     329        pio_write_8(ne2k->port + DP_MAR7, 0);
    341330       
    342331        pio_write_8(ne2k->port + DP_CURR, ne2k->start_page + 1);
     
    372361}
    373362
     363static void ne2k_reset(ne2k_t *ne2k)
     364{
     365        unsigned int i;
     366
     367        fibril_mutex_lock(&ne2k->sq_mutex);
     368
     369        /* Stop the chip */
     370        pio_write_8(ne2k->port + DP_CR, CR_STP | CR_DM_ABORT);
     371        pio_write_8(ne2k->port + DP_RBCR0, 0);
     372        pio_write_8(ne2k->port + DP_RBCR1, 0);
     373
     374        for (i = 0; i < NE2K_RETRY; i++) {
     375                if ((pio_read_8(ne2k->port + DP_ISR) & ISR_RST) != 0)
     376                        break;
     377        }
     378
     379        pio_write_8(ne2k->port + DP_TCR, TCR_1EXTERNAL | TCR_OFST);
     380        pio_write_8(ne2k->port + DP_CR, CR_STA | CR_DM_ABORT);
     381        pio_write_8(ne2k->port + DP_TCR, TCR_NORMAL);
     382
     383        /* Acknowledge the ISR_RDC (remote DMA) interrupt */
     384        for (i = 0; i < NE2K_RETRY; i++) {
     385                if ((pio_read_8(ne2k->port + DP_ISR) & ISR_RDC) != 0)
     386                        break;
     387        }
     388
     389        uint8_t val = pio_read_8(ne2k->port + DP_ISR);
     390        pio_write_8(ne2k->port + DP_ISR, val & ~ISR_RDC);
     391
     392        /*
     393         * Reset the transmit ring. If we were transmitting a frame,
     394         * we pretend that the packet is processed. Higher layers will
     395         * retransmit if the packet wasn't actually sent.
     396         */
     397        ne2k->sq.dirty = false;
     398
     399        fibril_mutex_unlock(&ne2k->sq_mutex);
     400}
     401
    374402/** Send a frame.
    375403 *
     
    378406 *
    379407 */
    380 void ne2k_send(ne2k_t *ne2k, packet_t *packet)
    381 {
     408void ne2k_send(nic_t *nic_data, packet_t *packet)
     409{
     410        ne2k_t *ne2k = (ne2k_t *) nic_get_specific(nic_data);
     411
    382412        assert(ne2k->probed);
    383413        assert(ne2k->up);
    384        
     414
    385415        fibril_mutex_lock(&ne2k->sq_mutex);
    386416       
    387         while (ne2k->sq.dirty)
     417        while (ne2k->sq.dirty) {
    388418                fibril_condvar_wait(&ne2k->sq_cv, &ne2k->sq_mutex);
    389        
     419        }
    390420        void *buf = packet_get_data(packet);
    391421        size_t size = packet_get_data_length(packet);
     
    393423        if ((size < ETH_MIN_PACK_SIZE) || (size > ETH_MAX_PACK_SIZE_TAGGED)) {
    394424                fibril_mutex_unlock(&ne2k->sq_mutex);
    395                 fprintf(stderr, "%s: Frame dropped (invalid size %zu bytes)\n",
    396                     NAME, size);
    397425                return;
    398426        }
    399        
     427
    400428        /* Upload the frame to the ethernet card */
    401429        ne2k_upload(ne2k, buf, ne2k->sq.page * DP_PAGE, size);
    402430        ne2k->sq.dirty = true;
    403431        ne2k->sq.size = size;
    404        
     432
    405433        /* Initialize the transfer */
    406434        pio_write_8(ne2k->port + DP_TPSR, ne2k->sq.page);
     
    408436        pio_write_8(ne2k->port + DP_TBCR1, (size >> 8) & 0xff);
    409437        pio_write_8(ne2k->port + DP_CR, CR_TXP | CR_STA);
    410        
    411438        fibril_mutex_unlock(&ne2k->sq_mutex);
    412 }
    413 
    414 static 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 
    451 static frame_t *ne2k_receive_frame(ne2k_t *ne2k, uint8_t page, size_t length)
    452 {
    453         frame_t *frame = (frame_t *) malloc(sizeof(frame_t));
     439
     440        /* Relase packet */
     441        nic_release_packet(nic_data, packet);
     442}
     443
     444static nic_frame_t *ne2k_receive_frame(nic_t *nic_data, uint8_t page,
     445        size_t length)
     446{
     447        ne2k_t *ne2k = (ne2k_t *) nic_get_specific(nic_data);
     448
     449        nic_frame_t *frame = nic_alloc_frame(nic_data, length);
    454450        if (frame == NULL)
    455451                return NULL;
    456        
    457         link_initialize(&frame->link);
    458        
    459         frame->packet = netif_packet_get_1(length);
    460         if (frame->packet == NULL) {
    461                 free(frame);
    462                 return NULL;
    463         }
    464452       
    465453        void *buf = packet_suffix(frame->packet, length);
     
    470458                size_t left = (ne2k->stop_page - page) * DP_PAGE
    471459                    - sizeof(recv_header_t);
    472                
    473460                ne2k_download(ne2k, buf, page * DP_PAGE + sizeof(recv_header_t),
    474461                    left);
    475462                ne2k_download(ne2k, buf + left, ne2k->start_page * DP_PAGE,
    476463                    length - left);
    477         } else
     464        } else {
    478465                ne2k_download(ne2k, buf, page * DP_PAGE + sizeof(recv_header_t),
    479466                    length);
    480        
    481         ne2k->stats.receive_packets++;
     467        }
    482468        return frame;
    483469}
    484470
    485 static list_t *ne2k_receive(ne2k_t *ne2k)
    486 {
     471static void ne2k_receive(nic_t *nic_data)
     472{
     473        ne2k_t *ne2k = (ne2k_t *) nic_get_specific(nic_data);
    487474        /*
    488475         * Allocate memory for the list of received frames.
     
    490477         * frames from the network, but they will be lost.
    491478         */
    492         list_t *frames = (list_t *) malloc(sizeof(list_t));
    493         if (frames != NULL)
    494                 list_initialize(frames);
    495        
    496         while (true) {
     479        nic_frame_list_t *frames = nic_alloc_frame_list();
     480        size_t frames_count = 0;
     481
     482        /* We may block sending in this loop - after so many received frames there
     483         * must be some interrupt pending (for the frames not yet downloaded) and
     484         * we will continue in its handler. */
     485        while (frames_count < 16) {
     486                //TODO: isn't some locking necessary here?
    497487                uint8_t boundary = pio_read_8(ne2k->port + DP_BNRY) + 1;
    498488               
     
    503493                uint8_t current = pio_read_8(ne2k->port + DP_CURR);
    504494                pio_write_8(ne2k->port + DP_CR, CR_PS_P0 | CR_STA);
    505                
    506495                if (current == boundary)
    507496                        /* No more frames to process */
     
    520509               
    521510                pio_read_buf_16(ne2k->data_port, (void *) &header, size);
    522                
     511
    523512                size_t length =
    524513                    (((size_t) header.rbcl) | (((size_t) header.rbch) << 8)) - size;
     
    527516                if ((length < ETH_MIN_PACK_SIZE)
    528517                    || (length > ETH_MAX_PACK_SIZE_TAGGED)) {
    529                         fprintf(stderr, "%s: Rant frame (%zu bytes)\n", NAME, length);
    530518                        next = current;
    531519                } else if ((header.next < ne2k->start_page)
    532520                    || (header.next > ne2k->stop_page)) {
    533                         fprintf(stderr, "%s: Malformed next frame %u\n", NAME,
    534                             header.next);
    535521                        next = current;
    536522                } else if (header.status & RSR_FO) {
     
    539525                         * reset the buffers.
    540526                         */
    541                         fprintf(stderr, "%s: FIFO overrun\n", NAME);
    542527                        ne2k->overruns++;
    543528                        next = current;
    544529                } else if ((header.status & RSR_PRX) && (ne2k->up)) {
    545530                        if (frames != NULL) {
    546                                 frame_t *frame = ne2k_receive_frame(ne2k, boundary, length);
    547                                 if (frame != NULL)
    548                                         list_append(&frame->link, frames);
    549                         }
     531                                nic_frame_t *frame =
     532                                        ne2k_receive_frame(nic_data, boundary, length);
     533                                if (frame != NULL) {
     534                                        nic_frame_list_append(frames, frame);
     535                                        frames_count++;
     536                                } else {
     537                                        break;
     538                                }
     539                        } else
     540                                break;
    550541                }
    551542               
     
    560551                else
    561552                        next--;
    562                
    563553                pio_write_8(ne2k->port + DP_BNRY, next);
    564554        }
    565        
    566         return frames;
    567 }
    568 
    569 list_t *ne2k_interrupt(ne2k_t *ne2k, uint8_t isr, uint8_t tsr)
    570 {
    571         /* List of received frames */
    572         list_t *frames = NULL;
    573        
     555        nic_received_frame_list(nic_data, frames);
     556}
     557
     558void ne2k_interrupt(nic_t *nic_data, uint8_t isr, uint8_t tsr)
     559{
     560        ne2k_t *ne2k = (ne2k_t *) nic_get_specific(nic_data);
     561
    574562        if (isr & (ISR_PTX | ISR_TXE)) {
    575                 if (isr & ISR_TXE)
    576                         ne2k->stats.send_errors++;
    577                 else {
    578                         if (tsr & TSR_PTX)
    579                                 ne2k->stats.send_packets++;
    580                        
    581                         if (tsr & TSR_COL)
    582                                 ne2k->stats.collisions++;
    583                        
    584                         if (tsr & TSR_ABT)
    585                                 ne2k->stats.send_aborted_errors++;
    586                        
    587                         if (tsr & TSR_CRS)
    588                                 ne2k->stats.send_carrier_errors++;
    589                        
    590                         if (tsr & TSR_FU) {
    591                                 ne2k->underruns++;
    592                                 if (ne2k->underruns < NE2K_ERL)
    593                                         fprintf(stderr, "%s: FIFO underrun\n", NAME);
    594                         }
    595                        
    596                         if (tsr & TSR_CDH) {
    597                                 ne2k->stats.send_heartbeat_errors++;
    598                                 if (ne2k->stats.send_heartbeat_errors < NE2K_ERL)
    599                                         fprintf(stderr, "%s: CD heartbeat failure\n", NAME);
    600                         }
    601                        
    602                         if (tsr & TSR_OWC)
    603                                 ne2k->stats.send_window_errors++;
     563                if (tsr & TSR_COL) {
     564                        nic_report_collisions(nic_data,
     565                                pio_read_8(ne2k->port + DP_NCR) & 15);
    604566                }
    605                
     567
     568                if (tsr & TSR_PTX) {
     569                        // TODO: fix number of sent bytes (but how?)
     570                        nic_report_send_ok(nic_data, 1, 0);
     571                } else if (tsr & TSR_ABT) {
     572                        nic_report_send_error(nic_data, NIC_SEC_ABORTED, 1);
     573                } else if (tsr & TSR_CRS) {
     574                        nic_report_send_error(nic_data, NIC_SEC_CARRIER_LOST, 1);
     575                } else if (tsr & TSR_FU) {
     576                        ne2k->underruns++;
     577                        // if (ne2k->underruns < NE2K_ERL) {
     578                        // }
     579                } else if (tsr & TSR_CDH) {
     580                        nic_report_send_error(nic_data, NIC_SEC_HEARTBEAT, 1);
     581                        // if (nic_data->stats.send_heartbeat_errors < NE2K_ERL) {
     582                        // }
     583                } else if (tsr & TSR_OWC) {
     584                        nic_report_send_error(nic_data, NIC_SEC_WINDOW_ERROR, 1);
     585                }
     586
    606587                fibril_mutex_lock(&ne2k->sq_mutex);
    607                
    608588                if (ne2k->sq.dirty) {
    609589                        /* Prepare the buffer for next packet */
     
    615595                } else {
    616596                        ne2k->misses++;
    617                         if (ne2k->misses < NE2K_ERL)
    618                                 fprintf(stderr, "%s: Spurious PTX interrupt\n", NAME);
     597                        // if (ne2k->misses < NE2K_ERL) {
     598                        // }
    619599                }
    620                
    621600                fibril_mutex_unlock(&ne2k->sq_mutex);
    622601        }
    623        
    624         if (isr & ISR_RXE)
    625                 ne2k->stats.receive_errors++;
    626        
     602
    627603        if (isr & ISR_CNT) {
    628                 ne2k->stats.receive_crc_errors +=
    629                     pio_read_8(ne2k->port + DP_CNTR0);
    630                 ne2k->stats.receive_frame_errors +=
    631                     pio_read_8(ne2k->port + DP_CNTR1);
    632                 ne2k->stats.receive_missed_errors +=
    633                     pio_read_8(ne2k->port + DP_CNTR2);
    634         }
    635        
    636         if (isr & ISR_PRX)
    637                 frames = ne2k_receive(ne2k);
    638        
     604                unsigned int errors;
     605                for (errors = pio_read_8(ne2k->port + DP_CNTR0); errors > 0; --errors)
     606                        nic_report_receive_error(nic_data, NIC_REC_CRC, 1);
     607                for (errors = pio_read_8(ne2k->port + DP_CNTR1); errors > 0; --errors)
     608                        nic_report_receive_error(nic_data, NIC_REC_FRAME_ALIGNMENT, 1);
     609                for (errors = pio_read_8(ne2k->port + DP_CNTR2); errors > 0; --errors)
     610                        nic_report_receive_error(nic_data, NIC_REC_MISSED, 1);
     611        }
     612        if (isr & ISR_PRX) {
     613                ne2k_receive(nic_data);
     614        }
    639615        if (isr & ISR_RST) {
    640616                /*
     
    648624        pio_write_8(ne2k->port + DP_IMR,
    649625            IMR_PRXE | IMR_PTXE | IMR_RXEE | IMR_TXEE | IMR_OVWE | IMR_CNTE);
    650        
    651         return frames;
     626}
     627
     628void ne2k_set_accept_bcast(ne2k_t *ne2k, int accept)
     629{
     630        if (accept)
     631                ne2k->receive_configuration |= RCR_AB;
     632        else
     633                ne2k->receive_configuration &= ~RCR_AB;
     634       
     635        pio_write_8(ne2k->port + DP_RCR, ne2k->receive_configuration);
     636}
     637
     638void ne2k_set_accept_mcast(ne2k_t *ne2k, int accept)
     639{
     640        if (accept)
     641                ne2k->receive_configuration |= RCR_AM;
     642        else
     643                ne2k->receive_configuration &= ~RCR_AM;
     644       
     645        pio_write_8(ne2k->port + DP_RCR, ne2k->receive_configuration);
     646}
     647
     648void ne2k_set_promisc_phys(ne2k_t *ne2k, int promisc)
     649{
     650        if (promisc)
     651                ne2k->receive_configuration |= RCR_PRO;
     652        else
     653                ne2k->receive_configuration &= ~RCR_PRO;
     654       
     655        pio_write_8(ne2k->port + DP_RCR, ne2k->receive_configuration);
     656}
     657
     658void ne2k_set_mcast_hash(ne2k_t *ne2k, uint64_t hash)
     659{
     660        /* Select Page 1 and stop all transfers */
     661        pio_write_8(ne2k->port + DP_CR, CR_PS_P1 | CR_DM_ABORT | CR_STP);
     662       
     663        pio_write_8(ne2k->port + DP_MAR0, (uint8_t) hash);
     664        pio_write_8(ne2k->port + DP_MAR1, (uint8_t) (hash >> 8));
     665        pio_write_8(ne2k->port + DP_MAR2, (uint8_t) (hash >> 16));
     666        pio_write_8(ne2k->port + DP_MAR3, (uint8_t) (hash >> 24));
     667        pio_write_8(ne2k->port + DP_MAR4, (uint8_t) (hash >> 32));
     668        pio_write_8(ne2k->port + DP_MAR5, (uint8_t) (hash >> 40));
     669        pio_write_8(ne2k->port + DP_MAR6, (uint8_t) (hash >> 48));
     670        pio_write_8(ne2k->port + DP_MAR7, (uint8_t) (hash >> 56));
     671       
     672        /* Select Page 0 and resume transfers */
     673        pio_write_8(ne2k->port + DP_CR, CR_PS_P0 | CR_DM_ABORT | CR_STA);
    652674}
    653675
Note: See TracChangeset for help on using the changeset viewer.