Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • uspace/drv/ohci/root_hub.c

    rb3f655f r1387692  
    3939
    4040#include "root_hub.h"
    41 #include "usb/classes/classes.h"
    42 #include <usb/request.h>
    43 #include <usb/classes/hub.h>
    44 
    45 static const usb_standard_device_descriptor_t ohci_rh_device_descriptor =
    46 {
    47                 .configuration_count = 1,
    48                 .descriptor_type = USB_DESCTYPE_DEVICE,
    49                 .device_class = USB_CLASS_HUB,
    50                 .device_protocol = 0,
    51                 .device_subclass = 0,
    52                 .device_version = 0,
    53                 .length = sizeof(usb_standard_device_descriptor_t),
    54                 /// \TODO this value is guessed
    55                 .max_packet_size = 8,
    56                 .vendor_id = 0x16db,
    57                 .product_id = 0x0001,
    58                 /// \TODO these values migt be different
    59                 .str_serial_number = 0,
    60                 .usb_spec_version = 0,
    61 };
    62 
    63 static const usb_standard_configuration_descriptor_t ohci_rh_conf_descriptor =
    64 {
    65         /// \TODO some values are default or guessed
    66         .attributes = 1<<7,
    67         .configuration_number = 1,
    68         .descriptor_type = USB_DESCTYPE_CONFIGURATION,
    69         .interface_count = 1,
    70         .length = sizeof(usb_standard_configuration_descriptor_t),
    71         .max_power = 100,
    72         .str_configuration = 0,
    73 };
    74 
    75 static const usb_standard_interface_descriptor_t ohci_rh_iface_descriptor =
    76 {
    77         .alternate_setting = 0,
    78         .descriptor_type = USB_DESCTYPE_INTERFACE,
    79         .endpoint_count = 1,
    80         .interface_class = USB_CLASS_HUB,
    81         /// \TODO is this correct?
    82         .interface_number = 1,
    83         .interface_protocol = 0,
    84         .interface_subclass = 0,
    85         .length = sizeof(usb_standard_interface_descriptor_t),
    86         .str_interface = 0,
    87 };
    88 
    89 static const usb_standard_endpoint_descriptor_t ohci_rh_ep_descriptor =
    90 {
    91         .attributes = USB_TRANSFER_INTERRUPT,
    92         .descriptor_type = USB_DESCTYPE_ENDPOINT,
    93         .endpoint_address = 1 + (1<<7),
    94         .length = sizeof(usb_standard_endpoint_descriptor_t),
    95         .max_packet_size = 8,
    96         .poll_interval = 255,
    97 };
    9841
    9942/** Root hub initialization
     
    10750        instance->device = dev;
    10851
    109 
    11052        usb_log_info("OHCI root hub with %d ports.\n", regs->rh_desc_a & 0xff);
    11153
    112         //start generic usb hub driver
    113        
    11454        /* TODO: implement */
    11555        return EOK;
    11656}
    11757/*----------------------------------------------------------------------------*/
    118 
    119 
    120 static int process_get_port_status_request(rh_t *instance, uint16_t port,
    121                 usb_transfer_batch_t * request){
    122         if(port<1 || port>instance->port_count)
    123                 return EINVAL;
    124         uint32_t * uint32_buffer = (uint32_t*)request->buffer;
    125         request->transfered_size = 4;
    126         uint32_buffer[0] = instance->registers->rh_port_status[port -1];
    127         return EOK;
    128 }
    129 
    130 static int process_get_hub_status_request(rh_t *instance,
    131                 usb_transfer_batch_t * request){
    132         uint32_t * uint32_buffer = (uint32_t*)request->buffer;
    133         //bits, 0,1,16,17
    134         request->transfered_size = 4;
    135         uint32_t mask = 1 & (1<<1) & (1<<16) & (1<<17);
    136         uint32_buffer[0] = mask & instance->registers->rh_status;
    137         return EOK;
    138 
    139 }
    140 
    141 static void usb_create_serialized_hub_descriptor(rh_t *instance, uint8_t ** out_result,
    142                 size_t * out_size) {
    143         //base size
    144         size_t size = 7;
    145         //variable size according to port count
    146         size_t var_size = instance->port_count / 8 +
    147                         ((instance->port_count % 8 > 0) ? 1 : 0);
    148         size += 2 * var_size;
    149         uint8_t * result = (uint8_t*) malloc(size);
    150         bzero(result,size);
    151         //size
    152         result[0] = size;
    153         //descriptor type
    154         result[1] = USB_DESCTYPE_HUB;
    155         result[2] = instance->port_count;
    156         uint32_t hub_desc_reg = instance->registers->rh_desc_a;
    157         result[3] =
    158                         ((hub_desc_reg >> 8) %2) +
    159                         (((hub_desc_reg >> 9) %2) << 1) +
    160                         (((hub_desc_reg >> 10) %2) << 2) +
    161                         (((hub_desc_reg >> 11) %2) << 3) +
    162                         (((hub_desc_reg >> 12) %2) << 4);
    163         result[4] = 0;
    164         result[5] = /*descriptor->pwr_on_2_good_time*/ 50;
    165         result[6] = 50;
    166 
    167         int port;
    168         for (port = 1; port <= instance->port_count; ++port) {
    169                 result[7 + port/8] +=
    170                                 ((instance->registers->rh_desc_b >> port)%2) << (port%8);
    171         }
    172         size_t i;
    173         for (i = 0; i < var_size; ++i) {
    174                 result[7 + var_size + i] = 255;
    175         }
    176         (*out_result) = result;
    177         (*out_size) = size;
    178 }
    179 
    180 
    181 static int process_get_status_request(rh_t *instance,
    182                 usb_transfer_batch_t * request)
    183 {
    184         size_t buffer_size = request->buffer_size;
    185         usb_device_request_setup_packet_t * request_packet =
    186                         (usb_device_request_setup_packet_t*)
    187                         request->setup_buffer;
    188 
    189         usb_hub_bm_request_type_t request_type = request_packet->request_type;
    190         if(buffer_size<4/*request_packet->length*/){///\TODO
    191                 usb_log_warning("requested more data than buffer size\n");
    192                 return EINVAL;
    193         }
    194 
    195         if(request_type == USB_HUB_REQ_TYPE_GET_HUB_STATUS)
    196                 return process_get_hub_status_request(instance, request);
    197         if(request_type == USB_HUB_REQ_TYPE_GET_PORT_STATUS)
    198                 return process_get_port_status_request(instance, request_packet->index,
    199                                 request);
    200         return ENOTSUP;
    201 }
    202 
    203 static void create_interrupt_mask(rh_t *instance, void ** buffer,
    204                 size_t * buffer_size){
    205         int bit_count = instance->port_count + 1;
    206         (*buffer_size) = (bit_count / 8) + (bit_count%8==0)?0:1;
    207         (*buffer) = malloc(*buffer_size);
    208         uint8_t * bitmap = (uint8_t*)(*buffer);
    209         uint32_t mask = (1<<16) + (1<<17);
    210         bzero(bitmap,(*buffer_size));
    211         if(instance->registers->rh_status & mask){
    212                 bitmap[0] = 1;
    213         }
    214         int port;
    215         mask = 0;
    216         int i;
    217         for(i=16;i<=20;++i)
    218                 mask += 1<<i;
    219         for(port = 1; port<=instance->port_count;++port){
    220                 if(mask & instance->registers->rh_port_status[port-1]){
    221                         bitmap[(port+1)/8] += 1<<(port%8);
    222                 }
    223         }
    224 }
    225 
    226 
    227 static int process_get_descriptor_request(rh_t *instance,
    228                 usb_transfer_batch_t *request){
    229         /// \TODO
    230         usb_device_request_setup_packet_t * setup_request =
    231                         (usb_device_request_setup_packet_t*)request->setup_buffer;
    232         size_t size;
    233         const void * result_descriptor;
    234         const uint16_t setup_request_value = setup_request->value_high;
    235                         //(setup_request->value_low << 8);
    236         bool del = false;
    237 
    238         switch (setup_request_value)
    239         {
    240         case USB_DESCTYPE_HUB: {
    241                 uint8_t * descriptor;
    242                 usb_create_serialized_hub_descriptor(
    243                     instance, &descriptor, &size);
    244                 result_descriptor = descriptor;
    245                 break;
    246         }
    247         case USB_DESCTYPE_DEVICE: {
    248                 usb_log_debug("USB_DESCTYPE_DEVICE\n");
    249                 result_descriptor = &ohci_rh_device_descriptor;
    250                 size = sizeof(ohci_rh_device_descriptor);
    251                 break;
    252         }
    253         case USB_DESCTYPE_CONFIGURATION: {
    254                 usb_log_debug("USB_DESCTYPE_CONFIGURATION\n");
    255                 usb_standard_configuration_descriptor_t * descriptor =
    256                                 malloc(sizeof(usb_standard_configuration_descriptor_t));
    257                 memcpy(descriptor, &ohci_rh_conf_descriptor,
    258                     sizeof(usb_standard_configuration_descriptor_t));
    259                 /// \TODO should this include device descriptor?
    260                 const size_t hub_descriptor_size = 7 +
    261                                 2* (instance->port_count / 8 +
    262                                 ((instance->port_count % 8 > 0) ? 1 : 0));
    263                 descriptor->total_length =
    264                                 sizeof(usb_standard_configuration_descriptor_t)+
    265                                 sizeof(usb_standard_endpoint_descriptor_t)+
    266                                 sizeof(usb_standard_interface_descriptor_t)+
    267                                 hub_descriptor_size;
    268                 result_descriptor = descriptor;
    269                 size = sizeof(usb_standard_configuration_descriptor_t);
    270                 del = true;
    271                 break;
    272         }
    273         case USB_DESCTYPE_INTERFACE: {
    274                 usb_log_debug("USB_DESCTYPE_INTERFACE\n");
    275                 result_descriptor = &ohci_rh_iface_descriptor;
    276                 size = sizeof(ohci_rh_iface_descriptor);
    277                 break;
    278         }
    279         case USB_DESCTYPE_ENDPOINT: {
    280                 usb_log_debug("USB_DESCTYPE_ENDPOINT\n");
    281                 result_descriptor = &ohci_rh_ep_descriptor;
    282                 size = sizeof(ohci_rh_ep_descriptor);
    283                 break;
    284         }
    285         default: {
    286                 usb_log_debug("USB_DESCTYPE_EINVAL %d \n",setup_request->value);
    287                 usb_log_debug("\ttype %d\n\trequest %d\n\tvalue %d\n\tindex %d\n\tlen %d\n ",
    288                                 setup_request->request_type,
    289                                 setup_request->request,
    290                                 setup_request_value,
    291                                 setup_request->index,
    292                                 setup_request->length
    293                                 );
    294                 return EINVAL;
    295         }
    296         }
    297 #if 0
    298         if(setup_request_value == USB_DESCTYPE_HUB){
    299                 usb_log_debug("USB_DESCTYPE_HUB\n");
    300                 //create hub descriptor
    301                 uint8_t * descriptor;
    302                 usb_create_serialized_hub_descriptor(instance,
    303                                 &descriptor, &size);
    304                 result_descriptor = descriptor;
    305         }else if(setup_request_value == USB_DESCTYPE_DEVICE){
    306                 //create std device descriptor
    307                 usb_log_debug("USB_DESCTYPE_DEVICE\n");
    308                 usb_standard_device_descriptor_t * descriptor =
    309                                 (usb_standard_device_descriptor_t*)
    310                                 malloc(sizeof(usb_standard_device_descriptor_t));
    311                 descriptor->configuration_count = 1;
    312                 descriptor->descriptor_type = USB_DESCTYPE_DEVICE;
    313                 descriptor->device_class = USB_CLASS_HUB;
    314                 descriptor->device_protocol = 0;
    315                 descriptor->device_subclass = 0;
    316                 descriptor->device_version = 0;
    317                 descriptor->length = sizeof(usb_standard_device_descriptor_t);
    318                 /// \TODO this value is guessed
    319                 descriptor->max_packet_size = 8;
    320                 descriptor->product_id = 0x0001;
    321                 /// \TODO these values migt be different
    322                 descriptor->str_serial_number = 0;
    323                 descriptor->str_serial_number = 0;
    324                 descriptor->usb_spec_version = 0;
    325                 descriptor->vendor_id = 0x16db;
    326                 result_descriptor = descriptor;
    327                 size = sizeof(usb_standard_device_descriptor_t);
    328         }else if(setup_request_value == USB_DESCTYPE_CONFIGURATION){
    329                 usb_log_debug("USB_DESCTYPE_CONFIGURATION\n");
    330                 usb_standard_configuration_descriptor_t * descriptor =
    331                                 (usb_standard_configuration_descriptor_t*)
    332                                 malloc(sizeof(usb_standard_configuration_descriptor_t));
    333                 /// \TODO some values are default or guessed
    334                 descriptor->attributes = 1<<7;
    335                 descriptor->configuration_number = 1;
    336                 descriptor->descriptor_type = USB_DESCTYPE_CONFIGURATION;
    337                 descriptor->interface_count = 1;
    338                 descriptor->length = sizeof(usb_standard_configuration_descriptor_t);
    339                 descriptor->max_power = 100;
    340                 descriptor->str_configuration = 0;
    341                 /// \TODO should this include device descriptor?
    342                 size_t hub_descriptor_size = 7 +
    343                                 2* (instance->port_count / 8 +
    344                                 ((instance->port_count % 8 > 0) ? 1 : 0));
    345                 descriptor->total_length =
    346                                 sizeof(usb_standard_configuration_descriptor_t)+
    347                                 sizeof(usb_standard_endpoint_descriptor_t)+
    348                                 sizeof(usb_standard_interface_descriptor_t)+
    349                                 hub_descriptor_size;
    350                 result_descriptor = descriptor;
    351                 size = sizeof(usb_standard_configuration_descriptor_t);
    352 
    353         }else if(setup_request_value == USB_DESCTYPE_INTERFACE){
    354                 usb_log_debug("USB_DESCTYPE_INTERFACE\n");
    355                 usb_standard_interface_descriptor_t * descriptor =
    356                                 (usb_standard_interface_descriptor_t*)
    357                                 malloc(sizeof(usb_standard_interface_descriptor_t));
    358                 descriptor->alternate_setting = 0;
    359                 descriptor->descriptor_type = USB_DESCTYPE_INTERFACE;
    360                 descriptor->endpoint_count = 1;
    361                 descriptor->interface_class = USB_CLASS_HUB;
    362                 /// \TODO is this correct?
    363                 descriptor->interface_number = 1;
    364                 descriptor->interface_protocol = 0;
    365                 descriptor->interface_subclass = 0;
    366                 descriptor->length = sizeof(usb_standard_interface_descriptor_t);
    367                 descriptor->str_interface = 0;
    368                 result_descriptor = descriptor;
    369                 size = sizeof(usb_standard_interface_descriptor_t);
    370         }else if(setup_request_value == USB_DESCTYPE_ENDPOINT){
    371                 usb_log_debug("USB_DESCTYPE_ENDPOINT\n");
    372                 usb_standard_endpoint_descriptor_t * descriptor =
    373                                 (usb_standard_endpoint_descriptor_t*)
    374                                 malloc(sizeof(usb_standard_endpoint_descriptor_t));
    375                 descriptor->attributes = USB_TRANSFER_INTERRUPT;
    376                 descriptor->descriptor_type = USB_DESCTYPE_ENDPOINT;
    377                 descriptor->endpoint_address = 1 + (1<<7);
    378                 descriptor->length = sizeof(usb_standard_endpoint_descriptor_t);
    379                 descriptor->max_packet_size = 8;
    380                 descriptor->poll_interval = 255;
    381                 result_descriptor = descriptor;
    382                 size = sizeof(usb_standard_endpoint_descriptor_t);
    383         }else{
    384                 usb_log_debug("USB_DESCTYPE_EINVAL %d \n",setup_request->value);
    385                 usb_log_debug("\ttype %d\n\trequest %d\n\tvalue %d\n\tindex %d\n\tlen %d\n ",
    386                                 setup_request->request_type,
    387                                 setup_request->request,
    388                                 setup_request_value,
    389                                 setup_request->index,
    390                                 setup_request->length
    391                                 );
    392                 return EINVAL;
    393         }
    394 #endif
    395         if(request->buffer_size < size){
    396                 size = request->buffer_size;
    397         }
    398         request->transfered_size = size;
    399         memcpy(request->buffer,result_descriptor,size);
    400         if (del)
    401                 free(result_descriptor);
    402         return EOK;
    403 }
    404 
    405 static int process_get_configuration_request(rh_t *instance,
    406                 usb_transfer_batch_t *request){
    407         //set and get configuration requests do not have any meaning, only dummy
    408         //values are returned
    409         if(request->buffer_size != 1)
    410                 return EINVAL;
    411         request->buffer[0] = 1;
    412         request->transfered_size = 1;
    413         return EOK;
    414 }
    415 
    416 static int process_hub_feature_set_request(rh_t *instance,
    417                 uint16_t feature, bool enable){
    418         if(feature > USB_HUB_FEATURE_C_HUB_OVER_CURRENT)
    419                 return EINVAL;
    420         instance->registers->rh_status =
    421                         enable ?
    422                         (instance->registers->rh_status | (1<<feature))
    423                         :
    424                         (instance->registers->rh_status & (~(1<<feature)));
    425         /// \TODO any error?
    426         return EOK;
    427 }
    428 
    429 static int process_port_feature_set_request(rh_t *instance,
    430                 uint16_t feature, uint16_t port, bool enable){
    431         if(feature > USB_HUB_FEATURE_C_PORT_RESET)
    432                 return EINVAL;
    433         if(port<1 || port>instance->port_count)
    434                 return EINVAL;
    435         instance->registers->rh_port_status[port - 1] =
    436                         enable ?
    437                         (instance->registers->rh_port_status[port - 1] | (1<<feature))
    438                         :
    439                         (instance->registers->rh_port_status[port - 1] & (~(1<<feature)));
    440         /// \TODO any error?
    441         return EOK;
    442 }
    443 
    444 static int process_address_set_request(rh_t *instance,
    445                 uint16_t address){
    446         instance->address = address;
    447         return EOK;
    448 }
    449 
    450 static int process_request_with_output(rh_t *instance,
    451                 usb_transfer_batch_t *request){
    452         usb_device_request_setup_packet_t * setup_request =
    453                         (usb_device_request_setup_packet_t*)request->setup_buffer;
    454         if(setup_request->request == USB_DEVREQ_GET_STATUS){
    455                 usb_log_debug("USB_DEVREQ_GET_STATUS\n");
    456                 return process_get_status_request(instance, request);
    457         }
    458         if(setup_request->request == USB_DEVREQ_GET_DESCRIPTOR){
    459                 usb_log_debug("USB_DEVREQ_GET_DESCRIPTOR\n");
    460                 return process_get_descriptor_request(instance, request);
    461         }
    462         if(setup_request->request == USB_DEVREQ_GET_CONFIGURATION){
    463                 usb_log_debug("USB_DEVREQ_GET_CONFIGURATION\n");
    464                 return process_get_configuration_request(instance, request);
    465         }
    466         return ENOTSUP;
    467 }
    468 
    469 static int process_request_with_input(rh_t *instance,
    470                 usb_transfer_batch_t *request){
    471         usb_device_request_setup_packet_t * setup_request =
    472                         (usb_device_request_setup_packet_t*)request->setup_buffer;
    473         request->transfered_size = 0;
    474         if(setup_request->request == USB_DEVREQ_SET_DESCRIPTOR){
    475                 return ENOTSUP;
    476         }
    477         if(setup_request->request == USB_DEVREQ_SET_CONFIGURATION){
    478                 //set and get configuration requests do not have any meaning,
    479                 //only dummy values are returned
    480                 return EOK;
    481         }
    482         return ENOTSUP;
    483 }
    484 
    485 
    486 static int process_request_without_data(rh_t *instance,
    487                 usb_transfer_batch_t *request){
    488         usb_device_request_setup_packet_t * setup_request =
    489                         (usb_device_request_setup_packet_t*)request->setup_buffer;
    490         request->transfered_size = 0;
    491         if(setup_request->request == USB_DEVREQ_CLEAR_FEATURE
    492                                 || setup_request->request == USB_DEVREQ_SET_FEATURE){
    493                 if(setup_request->request_type == USB_HUB_REQ_TYPE_SET_HUB_FEATURE){
    494                         usb_log_debug("USB_HUB_REQ_TYPE_SET_HUB_FEATURE\n");
    495                         return process_hub_feature_set_request(instance, setup_request->value,
    496                                         setup_request->request == USB_DEVREQ_SET_FEATURE);
    497                 }
    498                 if(setup_request->request_type == USB_HUB_REQ_TYPE_SET_PORT_FEATURE){
    499                         usb_log_debug("USB_HUB_REQ_TYPE_SET_PORT_FEATURE\n");
    500                         return process_port_feature_set_request(instance, setup_request->value,
    501                                         setup_request->index,
    502                                         setup_request->request == USB_DEVREQ_SET_FEATURE);
    503                 }
    504                 usb_log_debug("USB_HUB_REQ_TYPE_INVALID %d\n",setup_request->request_type);
    505                 return EINVAL;
    506         }
    507         if(setup_request->request == USB_DEVREQ_SET_ADDRESS){
    508                 usb_log_debug("USB_DEVREQ_SET_ADDRESS\n");
    509                 return process_address_set_request(instance, setup_request->value);
    510         }
    511         usb_log_debug("USB_DEVREQ_SET_ENOTSUP %d\n",setup_request->request_type);
    512         return ENOTSUP;
    513 }
    514 
    515 
    516 /**
    517  *
    518  * @param instance
    519  * @param request
    520  * @return
    521  */
    52258int rh_request(rh_t *instance, usb_transfer_batch_t *request)
    52359{
    52460        assert(instance);
    52561        assert(request);
    526         int opResult;
    527         if(request->transfer_type == USB_TRANSFER_CONTROL){
    528                 if (request->setup_buffer) {
    529                         usb_log_info("Root hub got CTRL packet: %s.\n",
    530                                 usb_debug_str_buffer((const uint8_t *)request->setup_buffer, 8, 8));
    531                         if(sizeof(usb_device_request_setup_packet_t)>request->setup_size){
    532                                 usb_log_error("setup packet too small\n");
    533                                 return EINVAL;
    534                         }
    535                         usb_device_request_setup_packet_t * setup_request =
    536                                         (usb_device_request_setup_packet_t*)request->setup_buffer;
    537                         if(
    538                                 setup_request->request == USB_DEVREQ_GET_STATUS
    539                                 || setup_request->request == USB_DEVREQ_GET_DESCRIPTOR
    540                                 || setup_request->request == USB_DEVREQ_GET_CONFIGURATION
    541                         ){
    542                                 usb_log_debug("processing request with output\n");
    543                                 opResult = process_request_with_output(instance,request);
    544                         }else if(
    545                                 setup_request->request == USB_DEVREQ_CLEAR_FEATURE
    546                                 || setup_request->request == USB_DEVREQ_SET_FEATURE
    547                                 || setup_request->request == USB_DEVREQ_SET_ADDRESS
    548                         ){
    549                                 usb_log_debug("processing request without additional data\n");
    550                                 opResult = process_request_without_data(instance,request);
    551                         }else if(setup_request->request == USB_DEVREQ_SET_DESCRIPTOR
    552                                         || setup_request->request == USB_DEVREQ_SET_CONFIGURATION
    553                         ){
    554                                 usb_log_debug("processing request with input\n");
    555                                 opResult = process_request_with_input(instance,request);
    556                         }else{
    557                                 usb_log_warning("received unsuported request: %d\n",
    558                                                 setup_request->request
    559                                                 );
    560                                 opResult = ENOTSUP;
    561                         }
    562                 }else{
    563                         usb_log_error("root hub received empty transaction?");
    564                         opResult = EINVAL;
    565                 }
    566         }else if(request->transfer_type == USB_TRANSFER_INTERRUPT){
    567                 usb_log_info("Root hub got INTERRUPT packet\n");
    568                 void * buffer;
    569                 create_interrupt_mask(instance, &buffer,
    570                         &(request->transfered_size));
    571                 memcpy(request->transport_buffer,buffer, request->transfered_size);
    572                 opResult = EOK;
    573         }else{
    574                 opResult = EINVAL;
     62        /* TODO: implement */
     63        if (request->setup_buffer) {
     64                usb_log_info("Root hub got SETUP packet: %s.\n",
     65                    usb_debug_str_buffer((const uint8_t *)request->setup_buffer, 8, 8));
    57566        }
    576         usb_transfer_batch_finish(request, opResult);
     67        usb_log_error("Root hub request processing not implemented.\n");
     68        usb_transfer_batch_finish(request, ENOTSUP);
    57769        return EOK;
    57870}
    57971/*----------------------------------------------------------------------------*/
    580 
    581 
    58272void rh_interrupt(rh_t *instance)
    58373{
Note: See TracChangeset for help on using the changeset viewer.