18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * Intel Wireless WiMAX Connection 2400m over USB 38c2ecf20Sopenharmony_ci * Notification handling 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * Copyright (C) 2007-2008 Intel Corporation. All rights reserved. 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Redistribution and use in source and binary forms, with or without 98c2ecf20Sopenharmony_ci * modification, are permitted provided that the following conditions 108c2ecf20Sopenharmony_ci * are met: 118c2ecf20Sopenharmony_ci * 128c2ecf20Sopenharmony_ci * * Redistributions of source code must retain the above copyright 138c2ecf20Sopenharmony_ci * notice, this list of conditions and the following disclaimer. 148c2ecf20Sopenharmony_ci * * Redistributions in binary form must reproduce the above copyright 158c2ecf20Sopenharmony_ci * notice, this list of conditions and the following disclaimer in 168c2ecf20Sopenharmony_ci * the documentation and/or other materials provided with the 178c2ecf20Sopenharmony_ci * distribution. 188c2ecf20Sopenharmony_ci * * Neither the name of Intel Corporation nor the names of its 198c2ecf20Sopenharmony_ci * contributors may be used to endorse or promote products derived 208c2ecf20Sopenharmony_ci * from this software without specific prior written permission. 218c2ecf20Sopenharmony_ci * 228c2ecf20Sopenharmony_ci * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 238c2ecf20Sopenharmony_ci * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 248c2ecf20Sopenharmony_ci * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 258c2ecf20Sopenharmony_ci * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 268c2ecf20Sopenharmony_ci * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 278c2ecf20Sopenharmony_ci * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 288c2ecf20Sopenharmony_ci * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 298c2ecf20Sopenharmony_ci * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 308c2ecf20Sopenharmony_ci * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 318c2ecf20Sopenharmony_ci * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 328c2ecf20Sopenharmony_ci * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 338c2ecf20Sopenharmony_ci * 348c2ecf20Sopenharmony_ci * 358c2ecf20Sopenharmony_ci * Intel Corporation <linux-wimax@intel.com> 368c2ecf20Sopenharmony_ci * Yanir Lubetkin <yanirx.lubetkin@intel.com> 378c2ecf20Sopenharmony_ci * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> 388c2ecf20Sopenharmony_ci * - Initial implementation 398c2ecf20Sopenharmony_ci * 408c2ecf20Sopenharmony_ci * 418c2ecf20Sopenharmony_ci * The notification endpoint is active when the device is not in boot 428c2ecf20Sopenharmony_ci * mode; in here we just read and get notifications; based on those, 438c2ecf20Sopenharmony_ci * we act to either reinitialize the device after a reboot or to 448c2ecf20Sopenharmony_ci * submit a RX request. 458c2ecf20Sopenharmony_ci * 468c2ecf20Sopenharmony_ci * ROADMAP 478c2ecf20Sopenharmony_ci * 488c2ecf20Sopenharmony_ci * i2400mu_usb_notification_setup() 498c2ecf20Sopenharmony_ci * 508c2ecf20Sopenharmony_ci * i2400mu_usb_notification_release() 518c2ecf20Sopenharmony_ci * 528c2ecf20Sopenharmony_ci * i2400mu_usb_notification_cb() Called when a URB is ready 538c2ecf20Sopenharmony_ci * i2400mu_notif_grok() 548c2ecf20Sopenharmony_ci * i2400m_is_boot_barker() 558c2ecf20Sopenharmony_ci * i2400m_dev_reset_handle() 568c2ecf20Sopenharmony_ci * i2400mu_rx_kick() 578c2ecf20Sopenharmony_ci */ 588c2ecf20Sopenharmony_ci#include <linux/usb.h> 598c2ecf20Sopenharmony_ci#include <linux/slab.h> 608c2ecf20Sopenharmony_ci#include "i2400m-usb.h" 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci#define D_SUBMODULE notif 648c2ecf20Sopenharmony_ci#include "usb-debug-levels.h" 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_cistatic const 688c2ecf20Sopenharmony_ci__le32 i2400m_ZERO_BARKER[4] = { 0, 0, 0, 0 }; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci/* 728c2ecf20Sopenharmony_ci * Process a received notification 738c2ecf20Sopenharmony_ci * 748c2ecf20Sopenharmony_ci * In normal operation mode, we can only receive two types of payloads 758c2ecf20Sopenharmony_ci * on the notification endpoint: 768c2ecf20Sopenharmony_ci * 778c2ecf20Sopenharmony_ci * - a reboot barker, we do a bootstrap (the device has reseted). 788c2ecf20Sopenharmony_ci * 798c2ecf20Sopenharmony_ci * - a block of zeroes: there is pending data in the IN endpoint 808c2ecf20Sopenharmony_ci */ 818c2ecf20Sopenharmony_cistatic 828c2ecf20Sopenharmony_ciint i2400mu_notification_grok(struct i2400mu *i2400mu, const void *buf, 838c2ecf20Sopenharmony_ci size_t buf_len) 848c2ecf20Sopenharmony_ci{ 858c2ecf20Sopenharmony_ci int ret; 868c2ecf20Sopenharmony_ci struct device *dev = &i2400mu->usb_iface->dev; 878c2ecf20Sopenharmony_ci struct i2400m *i2400m = &i2400mu->i2400m; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci d_fnstart(4, dev, "(i2400m %p buf %p buf_len %zu)\n", 908c2ecf20Sopenharmony_ci i2400mu, buf, buf_len); 918c2ecf20Sopenharmony_ci ret = -EIO; 928c2ecf20Sopenharmony_ci if (buf_len < sizeof(i2400m_ZERO_BARKER)) 938c2ecf20Sopenharmony_ci /* Not a bug, just ignore */ 948c2ecf20Sopenharmony_ci goto error_bad_size; 958c2ecf20Sopenharmony_ci ret = 0; 968c2ecf20Sopenharmony_ci if (!memcmp(i2400m_ZERO_BARKER, buf, sizeof(i2400m_ZERO_BARKER))) { 978c2ecf20Sopenharmony_ci i2400mu_rx_kick(i2400mu); 988c2ecf20Sopenharmony_ci goto out; 998c2ecf20Sopenharmony_ci } 1008c2ecf20Sopenharmony_ci ret = i2400m_is_boot_barker(i2400m, buf, buf_len); 1018c2ecf20Sopenharmony_ci if (unlikely(ret >= 0)) 1028c2ecf20Sopenharmony_ci ret = i2400m_dev_reset_handle(i2400m, "device rebooted"); 1038c2ecf20Sopenharmony_ci else /* Unknown or unexpected data in the notif message */ 1048c2ecf20Sopenharmony_ci i2400m_unknown_barker(i2400m, buf, buf_len); 1058c2ecf20Sopenharmony_cierror_bad_size: 1068c2ecf20Sopenharmony_ciout: 1078c2ecf20Sopenharmony_ci d_fnend(4, dev, "(i2400m %p buf %p buf_len %zu) = %d\n", 1088c2ecf20Sopenharmony_ci i2400mu, buf, buf_len, ret); 1098c2ecf20Sopenharmony_ci return ret; 1108c2ecf20Sopenharmony_ci} 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci/* 1148c2ecf20Sopenharmony_ci * URB callback for the notification endpoint 1158c2ecf20Sopenharmony_ci * 1168c2ecf20Sopenharmony_ci * @urb: the urb received from the notification endpoint 1178c2ecf20Sopenharmony_ci * 1188c2ecf20Sopenharmony_ci * This function will just process the USB side of the transaction, 1198c2ecf20Sopenharmony_ci * checking everything is fine, pass the processing to 1208c2ecf20Sopenharmony_ci * i2400m_notification_grok() and resubmit the URB. 1218c2ecf20Sopenharmony_ci */ 1228c2ecf20Sopenharmony_cistatic 1238c2ecf20Sopenharmony_civoid i2400mu_notification_cb(struct urb *urb) 1248c2ecf20Sopenharmony_ci{ 1258c2ecf20Sopenharmony_ci int ret; 1268c2ecf20Sopenharmony_ci struct i2400mu *i2400mu = urb->context; 1278c2ecf20Sopenharmony_ci struct device *dev = &i2400mu->usb_iface->dev; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci d_fnstart(4, dev, "(urb %p status %d actual_length %d)\n", 1308c2ecf20Sopenharmony_ci urb, urb->status, urb->actual_length); 1318c2ecf20Sopenharmony_ci ret = urb->status; 1328c2ecf20Sopenharmony_ci switch (ret) { 1338c2ecf20Sopenharmony_ci case 0: 1348c2ecf20Sopenharmony_ci ret = i2400mu_notification_grok(i2400mu, urb->transfer_buffer, 1358c2ecf20Sopenharmony_ci urb->actual_length); 1368c2ecf20Sopenharmony_ci if (ret == -EIO && edc_inc(&i2400mu->urb_edc, EDC_MAX_ERRORS, 1378c2ecf20Sopenharmony_ci EDC_ERROR_TIMEFRAME)) 1388c2ecf20Sopenharmony_ci goto error_exceeded; 1398c2ecf20Sopenharmony_ci if (ret == -ENOMEM) /* uff...power cycle? shutdown? */ 1408c2ecf20Sopenharmony_ci goto error_exceeded; 1418c2ecf20Sopenharmony_ci break; 1428c2ecf20Sopenharmony_ci case -EINVAL: /* while removing driver */ 1438c2ecf20Sopenharmony_ci case -ENODEV: /* dev disconnect ... */ 1448c2ecf20Sopenharmony_ci case -ENOENT: /* ditto */ 1458c2ecf20Sopenharmony_ci case -ESHUTDOWN: /* URB killed */ 1468c2ecf20Sopenharmony_ci case -ECONNRESET: /* disconnection */ 1478c2ecf20Sopenharmony_ci goto out; /* Notify around */ 1488c2ecf20Sopenharmony_ci default: /* Some error? */ 1498c2ecf20Sopenharmony_ci if (edc_inc(&i2400mu->urb_edc, 1508c2ecf20Sopenharmony_ci EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) 1518c2ecf20Sopenharmony_ci goto error_exceeded; 1528c2ecf20Sopenharmony_ci dev_err(dev, "notification: URB error %d, retrying\n", 1538c2ecf20Sopenharmony_ci urb->status); 1548c2ecf20Sopenharmony_ci } 1558c2ecf20Sopenharmony_ci usb_mark_last_busy(i2400mu->usb_dev); 1568c2ecf20Sopenharmony_ci ret = usb_submit_urb(i2400mu->notif_urb, GFP_ATOMIC); 1578c2ecf20Sopenharmony_ci switch (ret) { 1588c2ecf20Sopenharmony_ci case 0: 1598c2ecf20Sopenharmony_ci case -EINVAL: /* while removing driver */ 1608c2ecf20Sopenharmony_ci case -ENODEV: /* dev disconnect ... */ 1618c2ecf20Sopenharmony_ci case -ENOENT: /* ditto */ 1628c2ecf20Sopenharmony_ci case -ESHUTDOWN: /* URB killed */ 1638c2ecf20Sopenharmony_ci case -ECONNRESET: /* disconnection */ 1648c2ecf20Sopenharmony_ci break; /* just ignore */ 1658c2ecf20Sopenharmony_ci default: /* Some error? */ 1668c2ecf20Sopenharmony_ci dev_err(dev, "notification: cannot submit URB: %d\n", ret); 1678c2ecf20Sopenharmony_ci goto error_submit; 1688c2ecf20Sopenharmony_ci } 1698c2ecf20Sopenharmony_ci d_fnend(4, dev, "(urb %p status %d actual_length %d) = void\n", 1708c2ecf20Sopenharmony_ci urb, urb->status, urb->actual_length); 1718c2ecf20Sopenharmony_ci return; 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_cierror_exceeded: 1748c2ecf20Sopenharmony_ci dev_err(dev, "maximum errors in notification URB exceeded; " 1758c2ecf20Sopenharmony_ci "resetting device\n"); 1768c2ecf20Sopenharmony_cierror_submit: 1778c2ecf20Sopenharmony_ci usb_queue_reset_device(i2400mu->usb_iface); 1788c2ecf20Sopenharmony_ciout: 1798c2ecf20Sopenharmony_ci d_fnend(4, dev, "(urb %p status %d actual_length %d) = void\n", 1808c2ecf20Sopenharmony_ci urb, urb->status, urb->actual_length); 1818c2ecf20Sopenharmony_ci} 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci/* 1858c2ecf20Sopenharmony_ci * setup the notification endpoint 1868c2ecf20Sopenharmony_ci * 1878c2ecf20Sopenharmony_ci * @i2400m: device descriptor 1888c2ecf20Sopenharmony_ci * 1898c2ecf20Sopenharmony_ci * This procedure prepares the notification urb and handler for receiving 1908c2ecf20Sopenharmony_ci * unsolicited barkers from the device. 1918c2ecf20Sopenharmony_ci */ 1928c2ecf20Sopenharmony_ciint i2400mu_notification_setup(struct i2400mu *i2400mu) 1938c2ecf20Sopenharmony_ci{ 1948c2ecf20Sopenharmony_ci struct device *dev = &i2400mu->usb_iface->dev; 1958c2ecf20Sopenharmony_ci int usb_pipe, ret = 0; 1968c2ecf20Sopenharmony_ci struct usb_endpoint_descriptor *epd; 1978c2ecf20Sopenharmony_ci char *buf; 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci d_fnstart(4, dev, "(i2400m %p)\n", i2400mu); 2008c2ecf20Sopenharmony_ci buf = kmalloc(I2400MU_MAX_NOTIFICATION_LEN, GFP_KERNEL | GFP_DMA); 2018c2ecf20Sopenharmony_ci if (buf == NULL) { 2028c2ecf20Sopenharmony_ci ret = -ENOMEM; 2038c2ecf20Sopenharmony_ci goto error_buf_alloc; 2048c2ecf20Sopenharmony_ci } 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci i2400mu->notif_urb = usb_alloc_urb(0, GFP_KERNEL); 2078c2ecf20Sopenharmony_ci if (!i2400mu->notif_urb) { 2088c2ecf20Sopenharmony_ci ret = -ENOMEM; 2098c2ecf20Sopenharmony_ci goto error_alloc_urb; 2108c2ecf20Sopenharmony_ci } 2118c2ecf20Sopenharmony_ci epd = usb_get_epd(i2400mu->usb_iface, 2128c2ecf20Sopenharmony_ci i2400mu->endpoint_cfg.notification); 2138c2ecf20Sopenharmony_ci usb_pipe = usb_rcvintpipe(i2400mu->usb_dev, epd->bEndpointAddress); 2148c2ecf20Sopenharmony_ci usb_fill_int_urb(i2400mu->notif_urb, i2400mu->usb_dev, usb_pipe, 2158c2ecf20Sopenharmony_ci buf, I2400MU_MAX_NOTIFICATION_LEN, 2168c2ecf20Sopenharmony_ci i2400mu_notification_cb, i2400mu, epd->bInterval); 2178c2ecf20Sopenharmony_ci ret = usb_submit_urb(i2400mu->notif_urb, GFP_KERNEL); 2188c2ecf20Sopenharmony_ci if (ret != 0) { 2198c2ecf20Sopenharmony_ci dev_err(dev, "notification: cannot submit URB: %d\n", ret); 2208c2ecf20Sopenharmony_ci goto error_submit; 2218c2ecf20Sopenharmony_ci } 2228c2ecf20Sopenharmony_ci d_fnend(4, dev, "(i2400m %p) = %d\n", i2400mu, ret); 2238c2ecf20Sopenharmony_ci return ret; 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_cierror_submit: 2268c2ecf20Sopenharmony_ci usb_free_urb(i2400mu->notif_urb); 2278c2ecf20Sopenharmony_cierror_alloc_urb: 2288c2ecf20Sopenharmony_ci kfree(buf); 2298c2ecf20Sopenharmony_cierror_buf_alloc: 2308c2ecf20Sopenharmony_ci d_fnend(4, dev, "(i2400m %p) = %d\n", i2400mu, ret); 2318c2ecf20Sopenharmony_ci return ret; 2328c2ecf20Sopenharmony_ci} 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci/* 2368c2ecf20Sopenharmony_ci * Tear down of the notification mechanism 2378c2ecf20Sopenharmony_ci * 2388c2ecf20Sopenharmony_ci * @i2400m: device descriptor 2398c2ecf20Sopenharmony_ci * 2408c2ecf20Sopenharmony_ci * Kill the interrupt endpoint urb, free any allocated resources. 2418c2ecf20Sopenharmony_ci * 2428c2ecf20Sopenharmony_ci * We need to check if we have done it before as for example, 2438c2ecf20Sopenharmony_ci * _suspend() call this; if after a suspend() we get a _disconnect() 2448c2ecf20Sopenharmony_ci * (as the case is when hibernating), nothing bad happens. 2458c2ecf20Sopenharmony_ci */ 2468c2ecf20Sopenharmony_civoid i2400mu_notification_release(struct i2400mu *i2400mu) 2478c2ecf20Sopenharmony_ci{ 2488c2ecf20Sopenharmony_ci struct device *dev = &i2400mu->usb_iface->dev; 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci d_fnstart(4, dev, "(i2400mu %p)\n", i2400mu); 2518c2ecf20Sopenharmony_ci if (i2400mu->notif_urb != NULL) { 2528c2ecf20Sopenharmony_ci usb_kill_urb(i2400mu->notif_urb); 2538c2ecf20Sopenharmony_ci kfree(i2400mu->notif_urb->transfer_buffer); 2548c2ecf20Sopenharmony_ci usb_free_urb(i2400mu->notif_urb); 2558c2ecf20Sopenharmony_ci i2400mu->notif_urb = NULL; 2568c2ecf20Sopenharmony_ci } 2578c2ecf20Sopenharmony_ci d_fnend(4, dev, "(i2400mu %p)\n", i2400mu); 2588c2ecf20Sopenharmony_ci} 259