ports.c

Go to the documentation of this file.
00001 /*
00002  * Copyright (c) 2011 Vojtech Horky
00003  * All rights reserved.
00004  *
00005  * Redistribution and use in source and binary forms, with or without
00006  * modification, are permitted provided that the following conditions
00007  * are met:
00008  *
00009  * - Redistributions of source code must retain the above copyright
00010  *   notice, this list of conditions and the following disclaimer.
00011  * - Redistributions in binary form must reproduce the above copyright
00012  *   notice, this list of conditions and the following disclaimer in the
00013  *   documentation and/or other materials provided with the distribution.
00014  * - The name of the author may not be used to endorse or promote products
00015  *   derived from this software without specific prior written permission.
00016  *
00017  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
00018  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
00019  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
00020  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
00021  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
00022  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
00023  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
00024  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00025  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
00026  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00027  */
00028 
00036 #include <bool.h>
00037 #include <errno.h>
00038 #include <str_error.h>
00039 #include <inttypes.h>
00040 #include <fibril_synch.h>
00041 
00042 #include <usb/debug.h>
00043 
00044 #include "ports.h"
00045 #include "usbhub.h"
00046 #include "usbhub_private.h"
00047 #include "port_status.h"
00048 
00050 struct add_device_phase1 {
00051         usb_hub_info_t *hub;
00052         size_t port;
00053         usb_speed_t speed;
00054 };
00055 
00060 static const unsigned int non_handled_changes_count = 2;
00061 
00066 static const int non_handled_changes[] = {
00067         USB_HUB_FEATURE_C_PORT_ENABLE,
00068         USB_HUB_FEATURE_C_PORT_SUSPEND
00069 };
00070 
00071 static void usb_hub_removed_device(
00072     usb_hub_info_t *hub, uint16_t port);
00073 
00074 static void usb_hub_port_reset_completed(usb_hub_info_t *hub,
00075     uint16_t port, uint32_t status);
00076 
00077 static void usb_hub_port_over_current(usb_hub_info_t *hub,
00078     uint16_t port, uint32_t status);
00079 
00080 static int get_port_status(usb_pipe_t *ctrl_pipe, size_t port,
00081     usb_port_status_t *status);
00082 
00083 static int enable_port_callback(int port_no, void *arg);
00084 
00085 static int add_device_phase1_worker_fibril(void *arg);
00086 
00087 static int create_add_device_fibril(usb_hub_info_t *hub, size_t port,
00088     usb_speed_t speed);
00089 
00097 void usb_hub_process_port_interrupt(usb_hub_info_t *hub,
00098     uint16_t port) {
00099         usb_log_debug("Interrupt at port %zu\n", (size_t) port);
00100         //determine type of change
00101         //usb_pipe_t *pipe = hub->control_pipe;
00102 
00103         int opResult;
00104 
00105         usb_port_status_t status;
00106         opResult = get_port_status(&hub->usb_device->ctrl_pipe, port, &status);
00107         if (opResult != EOK) {
00108                 usb_log_error("Failed to get port %zu status: %s.\n",
00109                     (size_t) port, str_error(opResult));
00110                 return;
00111         }
00112         //connection change
00113         if (usb_port_is_status(status, USB_HUB_FEATURE_C_PORT_CONNECTION)) {
00114                 bool device_connected = usb_port_is_status(status,
00115                     USB_HUB_FEATURE_PORT_CONNECTION);
00116                 usb_log_debug("Connection change on port %zu: %s.\n",
00117                     (size_t) port,
00118                     device_connected ? "device attached" : "device removed");
00119 
00120                 if (device_connected) {
00121                         opResult = create_add_device_fibril(hub, port,
00122                             usb_port_speed(status));
00123                         if (opResult != EOK) {
00124                                 usb_log_error(
00125                                     "Cannot handle change on port %zu: %s.\n",
00126                                     (size_t) port, str_error(opResult));
00127                         }
00128                 } else {
00129                         usb_hub_removed_device(hub, port);
00130                 }
00131         }
00132         //over current
00133         if (usb_port_is_status(status, USB_HUB_FEATURE_C_PORT_OVER_CURRENT)) {
00134                 //check if it was not auto-resolved
00135                 usb_log_debug("Overcurrent change on port\n");
00136                 usb_hub_port_over_current(hub, port, status);
00137         }
00138         //port reset
00139         if (usb_port_is_status(status, USB_HUB_FEATURE_C_PORT_RESET)) {
00140                 usb_hub_port_reset_completed(hub, port, status);
00141         }
00142         usb_log_debug("Status x%x : %d\n ", status, status);
00143 
00144         usb_port_status_set_bit(
00145             &status, USB_HUB_FEATURE_C_PORT_CONNECTION, false);
00146         usb_port_status_set_bit(
00147             &status, USB_HUB_FEATURE_C_PORT_RESET, false);
00148         usb_port_status_set_bit(
00149             &status, USB_HUB_FEATURE_C_PORT_OVER_CURRENT, false);
00150 
00151         //clearing not yet handled changes      
00152         unsigned int feature_idx;
00153         for (feature_idx = 0;
00154             feature_idx < non_handled_changes_count;
00155             ++feature_idx) {
00156                 unsigned int bit_idx = non_handled_changes[feature_idx];
00157                 if (status & (1 << bit_idx)) {
00158                         usb_log_info(
00159                             "There was not yet handled change on port %d: %d"
00160                             ";clearing it\n",
00161                             port, bit_idx);
00162                         int opResult = usb_hub_clear_port_feature(
00163                             hub->control_pipe,
00164                             port, bit_idx);
00165                         if (opResult != EOK) {
00166                                 usb_log_warning(
00167                                     "Could not clear port flag %d: %s\n",
00168                                     bit_idx, str_error(opResult)
00169                                     );
00170                         }
00171                         usb_port_status_set_bit(
00172                             &status, bit_idx, false);
00173                 }
00174         }
00175         if (status >> 16) {
00176                 usb_log_info("There is still some unhandled change %X\n",
00177                     status);
00178         }
00179 }
00180 
00190 static void usb_hub_removed_device(
00191     usb_hub_info_t *hub, uint16_t port) {
00192 
00193         int opResult = usb_hub_clear_port_feature(hub->control_pipe,
00194             port, USB_HUB_FEATURE_C_PORT_CONNECTION);
00195         if (opResult != EOK) {
00196                 usb_log_warning("Could not clear port-change-connection flag\n");
00197         }
00202         //close address
00203 
00204         usb_hub_port_t *the_port = hub->ports + port;
00205 
00206         fibril_mutex_lock(&hub->port_mutex);
00207 
00208         if (the_port->attached_device.address >= 0) {
00209                 usb_log_warning("Device unplug on `%s' (port %zu): " \
00210                     "not implemented.\n", hub->usb_device->ddf_dev->name,
00211                     (size_t) port);
00212                 the_port->attached_device.address = -1;
00213                 the_port->attached_device.handle = 0;
00214         } else {
00215                 usb_log_warning("Device removed before being registered.\n");
00216 
00217                 /*
00218                  * Device was removed before port reset completed.
00219                  * We will announce a failed port reset to unblock the
00220                  * port reset callback from new device wrapper.
00221                  */
00222                 fibril_mutex_lock(&the_port->reset_mutex);
00223                 the_port->reset_completed = true;
00224                 the_port->reset_okay = false;
00225                 fibril_condvar_broadcast(&the_port->reset_cv);
00226                 fibril_mutex_unlock(&the_port->reset_mutex);
00227         }
00228 
00229         fibril_mutex_unlock(&hub->port_mutex);
00230 }
00231 
00241 static void usb_hub_port_reset_completed(usb_hub_info_t *hub,
00242     uint16_t port, uint32_t status) {
00243         usb_log_debug("Port %zu reset complete.\n", (size_t) port);
00244         if (usb_port_is_status(status, USB_HUB_FEATURE_PORT_ENABLE)) {
00245                 /* Finalize device adding. */
00246                 usb_hub_port_t *the_port = hub->ports + port;
00247                 fibril_mutex_lock(&the_port->reset_mutex);
00248                 the_port->reset_completed = true;
00249                 the_port->reset_okay = true;
00250                 fibril_condvar_broadcast(&the_port->reset_cv);
00251                 fibril_mutex_unlock(&the_port->reset_mutex);
00252         } else {
00253                 usb_log_warning(
00254                     "Port %zu reset complete but port not enabled.\n",
00255                     (size_t) port);
00256         }
00257         /* Clear the port reset change. */
00258         int rc = usb_hub_clear_port_feature(hub->control_pipe,
00259             port, USB_HUB_FEATURE_C_PORT_RESET);
00260         if (rc != EOK) {
00261                 usb_log_error("Failed to clear port %d reset feature: %s.\n",
00262                     port, str_error(rc));
00263         }
00264 }
00265 
00274 static void usb_hub_port_over_current(usb_hub_info_t *hub,
00275     uint16_t port, uint32_t status) {
00276         int opResult;
00277         if (usb_port_is_status(status, USB_HUB_FEATURE_PORT_OVER_CURRENT)) {
00278                 opResult = usb_hub_clear_port_feature(hub->control_pipe,
00279                     port, USB_HUB_FEATURE_PORT_POWER);
00280                 if (opResult != EOK) {
00281                         usb_log_error("Cannot power off port %d; %s\n",
00282                             port, str_error(opResult));
00283                 }
00284         } else {
00285                 opResult = usb_hub_set_port_feature(hub->control_pipe,
00286                     port, USB_HUB_FEATURE_PORT_POWER);
00287                 if (opResult != EOK) {
00288                         usb_log_error("Cannot power on port %d; %s\n",
00289                             port, str_error(opResult));
00290                 }
00291         }
00292 }
00293 
00301 static int get_port_status(usb_pipe_t *ctrl_pipe, size_t port,
00302     usb_port_status_t *status) {
00303         size_t recv_size;
00304         usb_device_request_setup_packet_t request;
00305         usb_port_status_t status_tmp;
00306 
00307         usb_hub_set_port_status_request(&request, port);
00308         int rc = usb_pipe_control_read(ctrl_pipe,
00309             &request, sizeof (usb_device_request_setup_packet_t),
00310             &status_tmp, sizeof (status_tmp), &recv_size);
00311         if (rc != EOK) {
00312                 return rc;
00313         }
00314 
00315         if (recv_size != sizeof (status_tmp)) {
00316                 return ELIMIT;
00317         }
00318 
00319         if (status != NULL) {
00320                 *status = status_tmp;
00321         }
00322 
00323         return EOK;
00324 }
00325 
00335 static int enable_port_callback(int port_no, void *arg) {
00336         usb_hub_info_t *hub = arg;
00337         int rc;
00338         usb_device_request_setup_packet_t request;
00339         usb_hub_port_t *my_port = hub->ports + port_no;
00340 
00341         usb_hub_set_reset_port_request(&request, port_no);
00342         rc = usb_pipe_control_write(hub->control_pipe,
00343             &request, sizeof (request), NULL, 0);
00344         if (rc != EOK) {
00345                 usb_log_warning("Port reset failed: %s.\n", str_error(rc));
00346                 return rc;
00347         }
00348 
00349         /*
00350          * Wait until reset completes.
00351          */
00352         fibril_mutex_lock(&my_port->reset_mutex);
00353         while (!my_port->reset_completed) {
00354                 fibril_condvar_wait(&my_port->reset_cv, &my_port->reset_mutex);
00355         }
00356         fibril_mutex_unlock(&my_port->reset_mutex);
00357 
00358         if (my_port->reset_okay) {
00359                 return EOK;
00360         } else {
00361                 return ESTALL;
00362         }
00363 }
00364 
00373 static int add_device_phase1_worker_fibril(void *arg) {
00374         struct add_device_phase1 *data
00375             = (struct add_device_phase1 *) arg;
00376 
00377         usb_address_t new_address;
00378         devman_handle_t child_handle;
00379 
00380         int rc = usb_hc_new_device_wrapper(data->hub->usb_device->ddf_dev,
00381             &data->hub->connection, data->speed,
00382             enable_port_callback, (int) data->port, data->hub,
00383             &new_address, &child_handle,
00384             NULL, NULL, NULL);
00385 
00386         if (rc != EOK) {
00387                 usb_log_error("Failed registering device on port %zu: %s.\n",
00388                     data->port, str_error(rc));
00389                 goto leave;
00390         }
00391 
00392         fibril_mutex_lock(&data->hub->port_mutex);
00393         data->hub->ports[data->port].attached_device.handle = child_handle;
00394         data->hub->ports[data->port].attached_device.address = new_address;
00395         fibril_mutex_unlock(&data->hub->port_mutex);
00396 
00397         usb_log_info("Detected new device on `%s' (port %zu), "
00398             "address %d (handle %" PRIun ").\n",
00399             data->hub->usb_device->ddf_dev->name, data->port,
00400             new_address, child_handle);
00401 
00402 leave:
00403         free(arg);
00404 
00405         fibril_mutex_lock(&data->hub->pending_ops_mutex);
00406         assert(data->hub->pending_ops_count > 0);
00407         data->hub->pending_ops_count--;
00408         fibril_condvar_signal(&data->hub->pending_ops_cv);
00409         fibril_mutex_unlock(&data->hub->pending_ops_mutex);
00410 
00411 
00412         return EOK;
00413 }
00414 
00424 static int create_add_device_fibril(usb_hub_info_t *hub, size_t port,
00425     usb_speed_t speed) {
00426         struct add_device_phase1 *data
00427             = malloc(sizeof (struct add_device_phase1));
00428         if (data == NULL) {
00429                 return ENOMEM;
00430         }
00431         data->hub = hub;
00432         data->port = port;
00433         data->speed = speed;
00434 
00435         usb_hub_port_t *the_port = hub->ports + port;
00436 
00437         fibril_mutex_lock(&the_port->reset_mutex);
00438         the_port->reset_completed = false;
00439         fibril_mutex_unlock(&the_port->reset_mutex);
00440 
00441         int rc = usb_hub_clear_port_feature(hub->control_pipe, port,
00442             USB_HUB_FEATURE_C_PORT_CONNECTION);
00443         if (rc != EOK) {
00444                 free(data);
00445                 usb_log_warning("Failed to clear port change flag: %s.\n",
00446                     str_error(rc));
00447                 return rc;
00448         }
00449 
00450         fid_t fibril = fibril_create(add_device_phase1_worker_fibril, data);
00451         if (fibril == 0) {
00452                 free(data);
00453                 return ENOMEM;
00454         }
00455         fibril_mutex_lock(&hub->pending_ops_mutex);
00456         hub->pending_ops_count++;
00457         fibril_mutex_unlock(&hub->pending_ops_mutex);
00458         fibril_add_ready(fibril);
00459 
00460         return EOK;
00461 }
00462 

Generated on Thu Jun 2 07:45:44 2011 for HelenOS/USB by  doxygen 1.4.7