| /**************************************************************************** |
| * drivers/usbhost/usbhost_xboxcontroller.c |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <debug.h> |
| #include <fcntl.h> |
| #include <poll.h> |
| |
| #include <nuttx/irq.h> |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/kthread.h> |
| #include <nuttx/fs/fs.h> |
| #include <nuttx/arch.h> |
| #include <nuttx/wqueue.h> |
| #include <nuttx/signal.h> |
| #include <nuttx/mutex.h> |
| |
| #include <nuttx/usb/usb.h> |
| #include <nuttx/usb/usbhost.h> |
| #include <nuttx/input/xbox-controller.h> |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| /* Configuration ************************************************************/ |
| |
| #ifndef CONFIG_SCHED_WORKQUEUE |
| # warning "Worker thread support is required (CONFIG_SCHED_WORKQUEUE)" |
| #endif |
| |
| #ifndef CONFIG_XBOXCONTROLLER_DEFPRIO |
| # define CONFIG_XBOXCONTROLLER_DEFPRIO 50 |
| #endif |
| |
| #ifndef CONFIG_XBOXCONTROLLER_STACKSIZE |
| # define CONFIG_XBOXCONTROLLER_STACKSIZE 1024 |
| #endif |
| |
| #ifndef CONFIG_XBOXCONTROLLER_NPOLLWAITERS |
| # define CONFIG_XBOXCONTROLLER_NPOLLWAITERS 2 |
| #endif |
| |
| /* Driver support ***********************************************************/ |
| |
| /* This format is used to construct the /dev/xbox[n] device driver path. It |
| * defined here so that it will be used consistently in all places. |
| */ |
| |
| #define DEV_FORMAT "/dev/xbox%c" |
| #define DEV_NAMELEN 11 |
| |
| /* Used in usbhost_cfgdesc() */ |
| |
| #define USBHOST_IFFOUND 0x01 |
| #define USBHOST_EPINFOUND 0x02 /* Required interrupt IN EP descriptor found */ |
| #define USBHOST_EPOUTFOUND 0x04 /* Required interrupt OUT EP descriptor found */ |
| #define USBHOST_ALLFOUND 0x07 |
| |
| #define USBHOST_MAX_CREFS 0x7fff |
| |
| /* Received message types */ |
| |
| #define USBHOST_WAITING_CONNECTION 0x02 |
| #define USBHOST_GUIDE_BUTTON_STATUS 0x07 |
| #define USBHOST_BUTTON_DATA 0x20 |
| |
| /* Button definitions */ |
| |
| #define XBOX_BUTTON_GUIDE_INDEX 4 |
| #define XBOX_BUTTON_SYNC_INDEX 4 |
| #define XBOX_BUTTON_SYNC_MASK (1 << 0) |
| #define XBOX_BUTTON_START_INDEX 4 |
| #define XBOX_BUTTON_START_MASK (1 << 2) |
| #define XBOX_BUTTON_BACK_INDEX 4 |
| #define XBOX_BUTTON_BACK_MASK (1 << 3) |
| #define XBOX_BUTTON_A_INDEX 4 |
| #define XBOX_BUTTON_A_MASK (1 << 4) |
| #define XBOX_BUTTON_B_INDEX 4 |
| #define XBOX_BUTTON_B_MASK (1 << 5) |
| #define XBOX_BUTTON_X_INDEX 4 |
| #define XBOX_BUTTON_X_MASK (1 << 6) |
| #define XBOX_BUTTON_Y_INDEX 4 |
| #define XBOX_BUTTON_Y_MASK (1 << 7) |
| #define XBOX_BUTTON_DPAD_UP_INDEX 5 |
| #define XBOX_BUTTON_DPAD_UP_MASK (1 << 0) |
| #define XBOX_BUTTON_DPAD_DOWN_INDEX 5 |
| #define XBOX_BUTTON_DPAD_DOWN_MASK (1 << 1) |
| #define XBOX_BUTTON_DPAD_LEFT_INDEX 5 |
| #define XBOX_BUTTON_DPAD_LEFT_MASK (1 << 2) |
| #define XBOX_BUTTON_DPAD_RIGHT_INDEX 5 |
| #define XBOX_BUTTON_DPAD_RIGHT_MASK (1 << 3) |
| #define XBOX_BUTTON_BUMPER_LEFT_INDEX 5 |
| #define XBOX_BUTTON_BUMPER_LEFT_MASK (1 << 4) |
| #define XBOX_BUTTON_BUMPER_RIGHT_INDEX 5 |
| #define XBOX_BUTTON_BUMPER_RIGHT_MASK (1 << 5) |
| #define XBOX_BUTTON_STICK_LEFT_INDEX 5 |
| #define XBOX_BUTTON_STICK_LEFT_MASK (1 << 6) |
| #define XBOX_BUTTON_STICK_RIGHT_INDEX 5 |
| #define XBOX_BUTTON_STICK_RIGHT_MASK (1 << 7) |
| #define XBOX_BUTTON_TRIGGER_LEFT 3 |
| #define XBOX_BUTTON_TRIGGER_RIGHT 4 |
| #define XBOX_BUTTON_STICK_LEFT_X 5 |
| #define XBOX_BUTTON_STICK_LEFT_Y 6 |
| #define XBOX_BUTTON_STICK_RIGHT_X 7 |
| #define XBOX_BUTTON_STICK_RIGHT_Y 8 |
| #define XBOX_BUTTON_SET(buffer, index, mask) \ |
| ((((buffer)[(index)] & (mask)) != 0) ? true : false); |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| /* This structure contains the internal, private state of the USB host class |
| * driver. |
| */ |
| |
| struct usbhost_state_s |
| { |
| /* This is the externally visible portion of the state */ |
| |
| struct usbhost_class_s usbclass; |
| |
| /* The remainder of the fields are provide to the class driver */ |
| |
| char devchar; /* Character identifying the /dev/xbox[n] device */ |
| volatile bool disconnected; /* TRUE: Device has been disconnected */ |
| volatile bool polling; /* TRUE: Poll thread is running */ |
| volatile bool open; /* TRUE: The controller device is open */ |
| volatile bool valid; /* TRUE: New sample data is available */ |
| volatile bool initialized; /* TRUE: The initialization packet has been sent */ |
| uint8_t ifno; /* Interface number */ |
| uint8_t nwaiters; /* Number of threads waiting for controller data */ |
| sem_t waitsem; /* Used to wait for controller data */ |
| int16_t crefs; /* Reference count on the driver instance */ |
| mutex_t lock; /* Used to maintain mutual exclusive access */ |
| struct work_s work; /* For interacting with the worker thread */ |
| FAR uint8_t *tbuffer; /* The allocated transfer buffer */ |
| uint8_t obuffer[20]; /* The fixed output transfer buffer */ |
| size_t tbuflen; /* Size of the allocated transfer buffer */ |
| usbhost_ep_t epin; /* IN endpoint */ |
| usbhost_ep_t epout; /* OUT endpoint */ |
| pid_t pollpid; /* PID of the poll task */ |
| size_t out_seq_num; /* The sequence number for outgoing packets */ |
| struct xbox_controller_buttonstate_s rpt; /* The latest report out of the controller. */ |
| |
| /* The following is a list if poll structures of threads waiting for |
| * driver events. The 'struct pollfd' reference for each open is also |
| * retained in the f_priv field of the 'struct file'. |
| */ |
| |
| struct pollfd *fds[CONFIG_XBOXCONTROLLER_NPOLLWAITERS]; |
| }; |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| /* Memory allocation services */ |
| |
| static inline FAR struct usbhost_state_s *usbhost_allocclass(void); |
| static inline void usbhost_freeclass(FAR struct usbhost_state_s *usbclass); |
| |
| /* Device name management */ |
| |
| static int usbhost_allocdevno(FAR struct usbhost_state_s *priv); |
| static void usbhost_freedevno(FAR struct usbhost_state_s *priv); |
| static inline void usbhost_mkdevname(FAR struct usbhost_state_s *priv, |
| FAR char *devname); |
| |
| /* Worker thread actions */ |
| |
| static void usbhost_destroy(FAR void *arg); |
| |
| /* Polling support */ |
| |
| static void usbhost_pollnotify(FAR struct usbhost_state_s *dev); |
| static int usbhost_xboxcontroller_poll(int argc, char *argv[]); |
| |
| /* Helpers for usbhost_connect() */ |
| |
| static inline int usbhost_cfgdesc(FAR struct usbhost_state_s *priv, |
| FAR const uint8_t *configdesc, |
| int desclen); |
| static inline int usbhost_devinit(FAR struct usbhost_state_s *priv); |
| |
| /* (Little Endian) Data helpers */ |
| |
| static inline uint16_t usbhost_getle16(const uint8_t *val); |
| static inline void usbhost_putle16(uint8_t *dest, uint16_t val); |
| |
| /* Transfer descriptor memory management */ |
| |
| static inline int usbhost_talloc(FAR struct usbhost_state_s *priv); |
| static inline int usbhost_tfree(FAR struct usbhost_state_s *priv); |
| |
| /* struct usbhost_registry_s methods */ |
| |
| static FAR struct usbhost_class_s * |
| usbhost_create(FAR struct usbhost_hubport_s *hport, |
| FAR const struct usbhost_id_s *id); |
| |
| /* struct usbhost_class_s methods */ |
| |
| static int usbhost_connect(FAR struct usbhost_class_s *usbclass, |
| FAR const uint8_t *configdesc, int desclen); |
| static int usbhost_disconnected(FAR struct usbhost_class_s *usbclass); |
| |
| /* Driver methods. We export the controller as a standard character driver */ |
| |
| static int usbhost_open(FAR struct file *filep); |
| static int usbhost_close(FAR struct file *filep); |
| static ssize_t usbhost_read(FAR struct file *filep, |
| FAR char *buffer, size_t len); |
| static ssize_t usbhost_write(FAR struct file *filep, |
| FAR const char *buffer, size_t len); |
| static int usbhost_ioctl(FAR struct file *filep, int cmd, unsigned long arg); |
| static int usbhost_poll(FAR struct file *filep, FAR struct pollfd *fds, |
| bool setup); |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| /* This structure provides the registry entry ID information that will be |
| * used to associate the USB class driver to a connected USB device. |
| */ |
| |
| static const struct usbhost_id_s g_xboxcontroller_id[] = |
| { |
| /* XBox One classic controller */ |
| |
| { |
| USB_CLASS_VENDOR_SPEC, /* base -- Must be one of the USB_CLASS_* definitions in usb.h */ |
| 0x0047, /* subclass -- depends on the device */ |
| 0x00d0, /* proto -- depends on the device */ |
| 0x045e, /* vid */ |
| 0x02dd /* pid */ |
| }, |
| |
| /* XBox One S controller */ |
| |
| { |
| USB_CLASS_VENDOR_SPEC, /* base -- Must be one of the USB_CLASS_* definitions in usb.h */ |
| 0x0047, /* subclass -- depends on the device */ |
| 0x00d0, /* proto -- depends on the device */ |
| 0x045e, /* vid */ |
| 0x02ea /* pid */ |
| } |
| }; |
| |
| /* This is the USB host storage class's registry entry */ |
| |
| static struct usbhost_registry_s g_xboxcontroller = |
| { |
| NULL, /* flink */ |
| usbhost_create, /* create */ |
| 2, /* nids */ |
| g_xboxcontroller_id /* id[] */ |
| }; |
| |
| /* The configuration information for the block file device. */ |
| |
| static const struct file_operations g_xboxcontroller_fops = |
| { |
| usbhost_open, /* open */ |
| usbhost_close, /* close */ |
| usbhost_read, /* read */ |
| usbhost_write, /* write */ |
| NULL, /* seek */ |
| usbhost_ioctl, /* ioctl */ |
| NULL, /* mmap */ |
| NULL, /* truncate */ |
| usbhost_poll /* poll */ |
| }; |
| |
| /* This is a bitmap that is used to allocate device names /dev/xboxa-z. */ |
| |
| static uint32_t g_devinuse; |
| |
| /* The following are used to managed the class creation operation */ |
| |
| static mutex_t g_lock = NXMUTEX_INITIALIZER; |
| static sem_t g_syncsem = SEM_INITIALIZER(0); |
| static FAR struct usbhost_state_s *g_priv; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: usbhost_allocclass |
| * |
| * Description: |
| * This is really part of the logic that implements the create() method |
| * of struct usbhost_registry_s. This function allocates memory for one |
| * new class instance. |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * On success, this function will return a non-NULL instance of struct |
| * usbhost_class_s. NULL is returned on failure; this function will |
| * will fail only if there are insufficient resources to create another |
| * USB host class instance. |
| * |
| ****************************************************************************/ |
| |
| static inline FAR struct usbhost_state_s *usbhost_allocclass(void) |
| { |
| FAR struct usbhost_state_s *priv; |
| |
| DEBUGASSERT(!up_interrupt_context()); |
| |
| priv = (FAR struct usbhost_state_s *) |
| kmm_malloc(sizeof(struct usbhost_state_s)); |
| |
| uinfo("Allocated: %p\n", priv); |
| return priv; |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_freeclass |
| * |
| * Description: |
| * Free a class instance previously allocated by usbhost_allocclass(). |
| * |
| * Input Parameters: |
| * usbclass - A reference to the class instance to be freed. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static inline void usbhost_freeclass(FAR struct usbhost_state_s *usbclass) |
| { |
| DEBUGASSERT(usbclass != NULL); |
| |
| /* Free the class instance (perhaps calling sched_kmm_free() in case we are |
| * executing from an interrupt handler. |
| */ |
| |
| uinfo("Freeing: %p\n", usbclass); |
| kmm_free(usbclass); |
| } |
| |
| /**************************************************************************** |
| * Name: Device name management |
| * |
| * Description: |
| * Some tiny functions to coordinate management of device names. |
| * |
| ****************************************************************************/ |
| |
| static int usbhost_allocdevno(FAR struct usbhost_state_s *priv) |
| { |
| irqstate_t flags; |
| int devno; |
| |
| flags = enter_critical_section(); |
| for (devno = 0; devno < 26; devno++) |
| { |
| uint32_t bitno = 1 << devno; |
| if ((g_devinuse & bitno) == 0) |
| { |
| g_devinuse |= bitno; |
| priv->devchar = 'a' + devno; |
| leave_critical_section(flags); |
| return OK; |
| } |
| } |
| |
| leave_critical_section(flags); |
| return -EMFILE; |
| } |
| |
| static void usbhost_freedevno(FAR struct usbhost_state_s *priv) |
| { |
| int devno = 'a' - priv->devchar; |
| |
| if (devno >= 0 && devno < 26) |
| { |
| irqstate_t flags = enter_critical_section(); |
| g_devinuse &= ~(1 << devno); |
| leave_critical_section(flags); |
| } |
| } |
| |
| static inline void usbhost_mkdevname(FAR struct usbhost_state_s *priv, |
| FAR char *devname) |
| { |
| snprintf(devname, DEV_NAMELEN, DEV_FORMAT, priv->devchar); |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_destroy |
| * |
| * Description: |
| * The USB device has been disconnected and the reference count on the USB |
| * host class instance has gone to 1.. Time to destroy the USB host class |
| * instance. |
| * |
| * Input Parameters: |
| * arg - A reference to the class instance to be destroyed. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void usbhost_destroy(FAR void *arg) |
| { |
| FAR struct usbhost_state_s *priv = (FAR struct usbhost_state_s *)arg; |
| FAR struct usbhost_hubport_s *hport; |
| char devname[DEV_NAMELEN]; |
| |
| DEBUGASSERT(priv != NULL && priv->usbclass.hport != NULL); |
| uinfo("crefs: %d\n", priv->crefs); |
| |
| hport = priv->usbclass.hport; |
| |
| DEBUGASSERT(hport->drvr); |
| |
| uinfo("crefs: %d\n", priv->crefs); |
| |
| /* Unregister the driver */ |
| |
| uinfo("Unregister driver\n"); |
| usbhost_mkdevname(priv, devname); |
| unregister_driver(devname); |
| |
| /* Release the device name used by this connection */ |
| |
| usbhost_freedevno(priv); |
| |
| /* Free the interrupt endpoints */ |
| |
| if (priv->epin) |
| { |
| DRVR_EPFREE(hport->drvr, priv->epin); |
| } |
| |
| /* Free any transfer buffers */ |
| |
| usbhost_tfree(priv); |
| |
| /* Destroy the semaphores & mutex */ |
| |
| nxmutex_destroy(&priv->lock); |
| nxsem_destroy(&priv->waitsem); |
| |
| /* Disconnect the USB host device */ |
| |
| DRVR_DISCONNECT(hport->drvr, hport); |
| |
| /* Free the function address assigned to this device */ |
| |
| usbhost_devaddr_destroy(hport, hport->funcaddr); |
| hport->funcaddr = 0; |
| |
| /* And free the class instance. */ |
| |
| usbhost_freeclass(priv); |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_pollnotify |
| * |
| * Description: |
| * Wake any threads waiting for controller data |
| * |
| * Input Parameters: |
| * priv - A reference to the controller state structure. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void usbhost_pollnotify(FAR struct usbhost_state_s *priv) |
| { |
| /* If there are threads waiting for read data, then signal one of them |
| * that the read data is available. |
| */ |
| |
| if (priv->nwaiters > 0) |
| { |
| nxsem_post(&priv->waitsem); |
| } |
| |
| /* If there are threads waiting on poll() for controller data to become |
| * available, then wake them up now. NOTE: we wake up all waiting threads |
| * because we do not know that they are going to do. If they all try to |
| * read the data, then some make end up blocking after all. |
| */ |
| |
| poll_notify(priv->fds, CONFIG_XBOXCONTROLLER_NPOLLWAITERS, POLLIN); |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_xboxcontroller_poll |
| * |
| * Description: |
| * Periodically check for new controller data. |
| * |
| * Input Parameters: |
| * arg - A reference to the class instance to be destroyed. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static int usbhost_xboxcontroller_poll(int argc, char *argv[]) |
| { |
| FAR struct usbhost_state_s *priv; |
| FAR struct usbhost_hubport_s *hport; |
| irqstate_t flags; |
| #if defined(CONFIG_DEBUG_USB) && defined(CONFIG_DEBUG_INFO) |
| unsigned int npolls = 0; |
| #endif |
| unsigned int nerrors = 0; |
| ssize_t nbytes; |
| int ret = OK; |
| |
| /* Synchronize with the start-up logic. Get the private instance, re-start |
| * the start-up logic, and wait a bit to make sure that all of the class |
| * creation logic has a chance to run to completion. |
| * |
| * NOTE: that the reference count is *not* incremented here. When the |
| * driver structure was created, it was created with a reference count of |
| * one. This thread is responsible for that count. The count will be |
| * decrement when this thread exits. |
| */ |
| |
| priv = g_priv; |
| DEBUGASSERT(priv != NULL && priv->usbclass.hport != NULL); |
| hport = priv->usbclass.hport; |
| |
| priv->polling = true; |
| nxsem_post(&g_syncsem); |
| nxsig_sleep(1); |
| |
| /* Loop here until the device is disconnected */ |
| |
| uinfo("Entering poll loop\n"); |
| |
| while (!priv->disconnected) |
| { |
| /* Read the next ccontroller report. We will stall here until the |
| * controller sends data. |
| */ |
| |
| nbytes = DRVR_TRANSFER(hport->drvr, priv->epin, |
| priv->tbuffer, priv->tbuflen); |
| |
| /* Check for errors -- Bail if an excessive number of consecutive |
| * errors are encountered. |
| */ |
| |
| if (nbytes < 0) |
| { |
| /* If DRVR_TRANSFER() returns EAGAIN, that simply means that |
| * the devices was not ready and has NAK'ed the transfer. That |
| * should not be treated as an error (unless it persists for a |
| * long time). |
| */ |
| |
| if (nbytes != -EAGAIN) |
| { |
| uerr("ERROR: DRVR_TRANSFER returned: %d/%u\n", |
| (int)nbytes, nerrors); |
| |
| if (++nerrors > 200) |
| { |
| uerr(" Too many errors... aborting: %d\n", nerrors); |
| ret = (int)nbytes; |
| break; |
| } |
| } |
| } |
| |
| /* The report was received correctly. */ |
| |
| else |
| { |
| /* Success, reset the error counter */ |
| |
| nerrors = 0; |
| |
| /* The type of message is in the first byte */ |
| |
| switch (priv->tbuffer[0]) |
| { |
| case USBHOST_WAITING_CONNECTION: |
| /* Send the initialization message when we received the |
| * the first waiting connection message. |
| */ |
| |
| if (!priv->initialized) |
| { |
| /* Get exclusive access to the controller state data */ |
| |
| ret = nxmutex_lock(&priv->lock); |
| if (ret < 0) |
| { |
| goto exitloop; |
| } |
| |
| priv->tbuffer[0] = 0x05; |
| priv->tbuffer[1] = 0x20; |
| priv->tbuffer[2] = priv->out_seq_num++; |
| priv->tbuffer[3] = 0x01; |
| priv->tbuffer[4] = 0x00; |
| nbytes = DRVR_TRANSFER(hport->drvr, priv->epout, |
| priv->tbuffer, 5); |
| priv->initialized = true; |
| |
| /* Release our lock on the state structure */ |
| |
| nxmutex_unlock(&priv->lock); |
| } |
| |
| break; |
| |
| case USBHOST_GUIDE_BUTTON_STATUS: |
| |
| /* Get exclusive access to the controller state data */ |
| |
| ret = nxmutex_lock(&priv->lock); |
| if (ret < 0) |
| { |
| goto exitloop; |
| } |
| |
| /* Read the data out of the controller report. */ |
| |
| priv->rpt.guide = |
| (priv->tbuffer[XBOX_BUTTON_GUIDE_INDEX] != 0) ? true : false; |
| priv->valid = true; |
| |
| /* The One X controller requires an ACK of the guide button |
| * status message. |
| */ |
| |
| if (priv->tbuffer[1] == 0x30) |
| { |
| static const uint8_t guide_button_report_ack[] = |
| { |
| 0x01, 0x20, 0x00, 0x09, 0x00, 0x07, 0x20, 0x02, |
| 0x00, 0x00, 0x00, 0x00, 0x00 |
| }; |
| |
| /* Remember the input packet sequence number. */ |
| |
| uint8_t seq_num = priv->tbuffer[2]; |
| |
| /* Copy the ACK packet into the transfer buffer. */ |
| |
| memcpy(priv->tbuffer, guide_button_report_ack, |
| sizeof(guide_button_report_ack)); |
| |
| /* Ensure the sequence number is the same as the input |
| * packet. |
| */ |
| |
| priv->tbuffer[2] = seq_num; |
| |
| /* Perform the transfer. */ |
| |
| nbytes = |
| DRVR_TRANSFER(hport->drvr, priv->epout, priv->tbuffer, |
| sizeof(guide_button_report_ack)); |
| } |
| |
| /* Notify any waiters that new controller data is available */ |
| |
| usbhost_pollnotify(priv); |
| |
| /* Release our lock on the state structure */ |
| |
| nxmutex_unlock(&priv->lock); |
| |
| break; |
| |
| case USBHOST_BUTTON_DATA: |
| |
| /* Ignore the controller data if no task has opened the |
| * driver. |
| */ |
| |
| if (priv->open) |
| { |
| /* Get exclusive access to the controller state data */ |
| |
| ret = nxmutex_lock(&priv->lock); |
| if (ret < 0) |
| { |
| goto exitloop; |
| } |
| |
| /* Read the data out of the controller report. */ |
| |
| priv->rpt.sync = |
| XBOX_BUTTON_SET(priv->tbuffer, |
| XBOX_BUTTON_SYNC_INDEX, |
| XBOX_BUTTON_SYNC_MASK); |
| priv->rpt.start = |
| XBOX_BUTTON_SET(priv->tbuffer, |
| XBOX_BUTTON_START_INDEX, |
| XBOX_BUTTON_START_MASK); |
| priv->rpt.back = |
| XBOX_BUTTON_SET(priv->tbuffer, |
| XBOX_BUTTON_BACK_INDEX, |
| XBOX_BUTTON_BACK_MASK); |
| priv->rpt.a = |
| XBOX_BUTTON_SET(priv->tbuffer, |
| XBOX_BUTTON_A_INDEX, |
| XBOX_BUTTON_A_MASK); |
| priv->rpt.b = |
| XBOX_BUTTON_SET(priv->tbuffer, |
| XBOX_BUTTON_B_INDEX, |
| XBOX_BUTTON_B_MASK); |
| priv->rpt.x = |
| XBOX_BUTTON_SET(priv->tbuffer, |
| XBOX_BUTTON_X_INDEX, |
| XBOX_BUTTON_X_MASK); |
| priv->rpt.y = |
| XBOX_BUTTON_SET(priv->tbuffer, |
| XBOX_BUTTON_Y_INDEX, |
| XBOX_BUTTON_Y_MASK); |
| priv->rpt.dpad_up = |
| XBOX_BUTTON_SET(priv->tbuffer, |
| XBOX_BUTTON_DPAD_UP_INDEX, |
| XBOX_BUTTON_DPAD_UP_MASK); |
| priv->rpt.dpad_down = |
| XBOX_BUTTON_SET(priv->tbuffer, |
| XBOX_BUTTON_DPAD_DOWN_INDEX, |
| XBOX_BUTTON_DPAD_DOWN_MASK); |
| priv->rpt.dpad_left = |
| XBOX_BUTTON_SET(priv->tbuffer, |
| XBOX_BUTTON_DPAD_LEFT_INDEX, |
| XBOX_BUTTON_DPAD_LEFT_MASK); |
| priv->rpt.dpad_right = |
| XBOX_BUTTON_SET(priv->tbuffer, |
| XBOX_BUTTON_DPAD_RIGHT_INDEX, |
| XBOX_BUTTON_DPAD_RIGHT_MASK); |
| priv->rpt.bumper_left = |
| XBOX_BUTTON_SET(priv->tbuffer, |
| XBOX_BUTTON_BUMPER_LEFT_INDEX, |
| XBOX_BUTTON_BUMPER_LEFT_MASK); |
| priv->rpt.bumper_right = |
| XBOX_BUTTON_SET(priv->tbuffer, |
| XBOX_BUTTON_BUMPER_RIGHT_INDEX, |
| XBOX_BUTTON_BUMPER_RIGHT_MASK); |
| priv->rpt.stick_click_left = |
| XBOX_BUTTON_SET(priv->tbuffer, |
| XBOX_BUTTON_STICK_LEFT_INDEX, |
| XBOX_BUTTON_STICK_LEFT_MASK); |
| priv->rpt.stick_click_right = |
| XBOX_BUTTON_SET(priv->tbuffer, |
| XBOX_BUTTON_STICK_RIGHT_INDEX, |
| XBOX_BUTTON_STICK_RIGHT_MASK); |
| |
| priv->rpt.trigger_left = |
| ((int16_t *)(priv->tbuffer))[XBOX_BUTTON_TRIGGER_LEFT]; |
| priv->rpt.trigger_right = |
| ((int16_t *)(priv->tbuffer))[XBOX_BUTTON_TRIGGER_RIGHT]; |
| priv->rpt.stick_left_x = |
| ((int16_t *)(priv->tbuffer))[XBOX_BUTTON_STICK_LEFT_X]; |
| priv->rpt.stick_left_y = |
| ((int16_t *)(priv->tbuffer))[XBOX_BUTTON_STICK_LEFT_Y]; |
| priv->rpt.stick_right_x = |
| ((int16_t *)(priv->tbuffer))[XBOX_BUTTON_STICK_RIGHT_X]; |
| priv->rpt.stick_right_y = |
| ((int16_t *)(priv->tbuffer))[XBOX_BUTTON_STICK_RIGHT_Y]; |
| |
| priv->valid = true; |
| |
| /* Notify any waiters that new controller data is |
| * available. |
| */ |
| |
| usbhost_pollnotify(priv); |
| |
| /* Release our lock on the state structure */ |
| |
| nxmutex_unlock(&priv->lock); |
| } |
| |
| break; |
| |
| default: |
| uinfo("Received message type: %x\n", priv->tbuffer[0]); |
| } |
| } |
| |
| /* If USB debug is on, then provide some periodic indication that |
| * polling is still happening. |
| */ |
| |
| #if defined(CONFIG_DEBUG_USB) && defined(CONFIG_DEBUG_INFO) |
| npolls++; |
| if ((npolls & 31) == 0) |
| { |
| uinfo("Still polling: %d\n", npolls); |
| } |
| #endif |
| } |
| |
| exitloop: |
| /* We get here when the driver is removed, when too many errors have |
| * been encountered, or when the thread is canceled. |
| * |
| * Make sure that we have exclusive access to the private data structure. |
| * There may now be other tasks with the character driver open and actively |
| * trying to interact with the class driver. |
| */ |
| |
| nxmutex_lock(&priv->lock); |
| |
| /* Indicate that we are no longer running and decrement the reference |
| * count held by this thread. If there are no other users of the class, |
| * we can destroy it now. Otherwise, we have to wait until the all |
| * of the file descriptors are closed. |
| */ |
| |
| uinfo("Controller removed, polling halted\n"); |
| |
| flags = enter_critical_section(); |
| priv->polling = false; |
| |
| /* Decrement the reference count held by this thread. */ |
| |
| DEBUGASSERT(priv->crefs > 0); |
| priv->crefs--; |
| |
| /* There are two possibilities: |
| * 1) The reference count is greater than zero. This means that there |
| * are still open references to the controller driver. In this case |
| * we need to wait until usbhost_close() is called and all of the |
| * open driver references are decremented. Then usbhost_destroy() can |
| * be called from usbhost_close(). |
| * 2) The reference count is now zero. This means that there are no |
| * further open references and we can call usbhost_destroy() now. |
| */ |
| |
| if (priv->crefs < 1) |
| { |
| /* Unregister the driver and destroy the instance (while we hold |
| * the semaphore!) |
| */ |
| |
| usbhost_destroy(priv); |
| } |
| else |
| { |
| /* No, we will destroy the driver instance when it is final open |
| * reference is closed |
| */ |
| |
| nxmutex_unlock(&priv->lock); |
| } |
| |
| leave_critical_section(flags); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_sample |
| * |
| * Description: |
| * Check if new controller data is available |
| * |
| * Input Parameters: |
| * priv - controller state instance |
| * sample - The location to return the sample data |
| * |
| ****************************************************************************/ |
| |
| static int usbhost_sample(FAR struct usbhost_state_s *priv, |
| FAR struct xbox_controller_buttonstate_s *sample) |
| { |
| irqstate_t flags; |
| int ret = -EAGAIN; |
| |
| /* Interrupts must be disabled when this is called to (1) prevent posting |
| * of semaphores from interrupt handlers, and (2) to prevent sampled data |
| * from changing until it has been reported. |
| */ |
| |
| flags = enter_critical_section(); |
| |
| /* Is there new mouse data available? */ |
| |
| if (priv->valid) |
| { |
| /* Return a copy of the sampled data. */ |
| |
| memcpy(sample, &priv->rpt, |
| sizeof(struct xbox_controller_buttonstate_s)); |
| |
| /* The sample has been reported and is no longer valid */ |
| |
| priv->valid = false; |
| ret = OK; |
| } |
| |
| leave_critical_section(flags); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_waitsample |
| * |
| * Description: |
| * Wait for the next valid controller sample |
| * |
| * Input Parameters: |
| * priv - controller state instance |
| * sample - The location to return the sample data |
| * |
| ****************************************************************************/ |
| |
| static int usbhost_waitsample(FAR struct usbhost_state_s *priv, |
| FAR struct xbox_controller_buttonstate_s *sample) |
| { |
| irqstate_t flags; |
| int ret; |
| |
| /* Interrupts must be disabled when this is called to (1) prevent posting |
| * of semaphores from interrupt handlers, and (2) to prevent sampled data |
| * from changing until it has been reported. |
| */ |
| |
| flags = enter_critical_section(); |
| |
| /* Now release the semaphore that manages mutually exclusive access to |
| * the device structure. This may cause other tasks to become ready to |
| * run, but they cannot run yet because pre-emption is disabled. |
| */ |
| |
| nxmutex_unlock(&priv->lock); |
| |
| /* Try to get the a sample... if we cannot, then wait on the semaphore |
| * that is posted when new sample data is available. |
| */ |
| |
| while (usbhost_sample(priv, sample) < 0) |
| { |
| /* Wait for a change in the HIDMOUSE state */ |
| |
| iinfo("Waiting..\n"); |
| priv->nwaiters++; |
| ret = nxsem_wait(&priv->waitsem); |
| priv->nwaiters--; |
| |
| if (ret < 0) |
| { |
| ierr("ERROR: nxsem_wait: %d\n", ret); |
| goto errout; |
| } |
| |
| /* Did the controller become disconnected while we were waiting */ |
| |
| if (priv->disconnected) |
| { |
| ret = -ENODEV; |
| goto errout; |
| } |
| } |
| |
| iinfo("Sampled\n"); |
| |
| /* Re-acquire the semaphore that manages mutually exclusive access to |
| * the device structure. We may have to wait here. But we have our |
| * sample. Interrupts and pre-emption will be re-enabled while we wait. |
| */ |
| |
| ret = nxmutex_lock(&priv->lock); |
| |
| errout: |
| /* Then re-enable interrupts. We might get interrupt here and there |
| * could be a new sample. But no new threads will run because we still |
| * have pre-emption disabled. |
| */ |
| |
| leave_critical_section(flags); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_cfgdesc |
| * |
| * Description: |
| * This function implements the connect() method of struct |
| * usbhost_class_s. This method is a callback into the class |
| * implementation. It is used to provide the device's configuration |
| * descriptor to the class so that the class may initialize properly |
| * |
| * Input Parameters: |
| * priv - The USB host class instance. |
| * configdesc - A pointer to a uint8_t buffer container the configuration |
| * descriptor. |
| * desclen - The length in bytes of the configuration descriptor. |
| * |
| * Returned Value: |
| * On success, zero (OK) is returned. On a failure, a negated errno value |
| * is returned indicating the nature of the failure |
| * |
| * Assumptions: |
| * This function will *not* be called from an interrupt handler. |
| * |
| ****************************************************************************/ |
| |
| static inline int usbhost_cfgdesc(FAR struct usbhost_state_s *priv, |
| FAR const uint8_t *configdesc, int desclen) |
| { |
| FAR struct usbhost_hubport_s *hport; |
| FAR struct usb_cfgdesc_s *cfgdesc; |
| FAR struct usb_desc_s *desc; |
| FAR struct usbhost_epdesc_s epindesc; |
| FAR struct usbhost_epdesc_s epoutdesc; |
| int remaining; |
| uint8_t found = 0; |
| bool done = false; |
| int ret; |
| |
| DEBUGASSERT(priv != NULL && priv->usbclass.hport && |
| configdesc != NULL && desclen >= sizeof(struct usb_cfgdesc_s)); |
| hport = priv->usbclass.hport; |
| |
| /* Keep the compiler from complaining about uninitialized variables */ |
| |
| memset(&epindesc, 0, sizeof(struct usbhost_epdesc_s)); |
| memset(&epoutdesc, 0, sizeof(struct usbhost_epdesc_s)); |
| |
| /* Verify that we were passed a configuration descriptor */ |
| |
| cfgdesc = (FAR struct usb_cfgdesc_s *)configdesc; |
| if (cfgdesc->type != USB_DESC_TYPE_CONFIG) |
| { |
| return -EINVAL; |
| } |
| |
| /* Get the total length of the configuration descriptor (little endian). |
| * It might be a good check to get the number of interfaces here too. |
| */ |
| |
| remaining = (int)usbhost_getle16(cfgdesc->totallen); |
| |
| /* Skip to the next entry descriptor */ |
| |
| configdesc += cfgdesc->len; |
| remaining -= cfgdesc->len; |
| |
| /* Loop where there are more dscriptors to examine */ |
| |
| while (remaining >= sizeof(struct usb_desc_s) && !done) |
| { |
| /* What is the next descriptor? */ |
| |
| desc = (FAR struct usb_desc_s *)configdesc; |
| switch (desc->type) |
| { |
| /* Interface descriptor. We really should get the number of endpoints |
| * from this descriptor too. |
| */ |
| |
| case USB_DESC_TYPE_INTERFACE: |
| { |
| uinfo("Interface descriptor\n"); |
| DEBUGASSERT(remaining >= USB_SIZEOF_IFDESC); |
| |
| /* Did we already find what we needed from a preceding |
| * interface? |
| */ |
| |
| if ((found & USBHOST_ALLFOUND) == USBHOST_ALLFOUND) |
| { |
| /* Yes.. then break out of the loop and use the preceding |
| * interface. |
| */ |
| |
| done = true; |
| } |
| else |
| { |
| /* Otherwise, discard any endpoints previously found */ |
| |
| found = USBHOST_IFFOUND; |
| } |
| } |
| break; |
| |
| /* Endpoint descriptor. Here, we expect two bulk endpoints, an IN |
| * and an OUT. |
| */ |
| |
| case USB_DESC_TYPE_ENDPOINT: |
| { |
| FAR struct usb_epdesc_s *epdesc = |
| (FAR struct usb_epdesc_s *)configdesc; |
| |
| uinfo("Endpoint descriptor\n"); |
| DEBUGASSERT(remaining >= USB_SIZEOF_EPDESC); |
| |
| /* Check for a interrupt endpoint. */ |
| |
| if ((epdesc->attr & USB_EP_ATTR_XFERTYPE_MASK) == |
| USB_EP_ATTR_XFER_INT) |
| { |
| /* Yes.. it is a interrupt endpoint. IN or OUT? */ |
| |
| if (USB_ISEPOUT(epdesc->addr)) |
| { |
| /* It is an OUT interrupt endpoint. There should be only |
| * one interrupt OUT endpoint. |
| */ |
| |
| if ((found & USBHOST_EPOUTFOUND) != 0) |
| { |
| /* Oops.. more than one endpoint. We don't know |
| * what to do with this. |
| */ |
| |
| return -EINVAL; |
| } |
| |
| found |= USBHOST_EPOUTFOUND; |
| |
| /* Save the bulk OUT endpoint information */ |
| |
| epoutdesc.hport = hport; |
| epoutdesc.addr = epdesc->addr & |
| USB_EP_ADDR_NUMBER_MASK; |
| epoutdesc.in = false; |
| epoutdesc.xfrtype = USB_EP_ATTR_XFER_INT; |
| epoutdesc.interval = epdesc->interval; |
| epoutdesc.mxpacketsize = |
| usbhost_getle16(epdesc->mxpacketsize); |
| |
| uerr("Interrupt OUT EP addr:%d mxpacketsize:%d\n", |
| epoutdesc.addr, epoutdesc.mxpacketsize); |
| } |
| else |
| { |
| /* It is an IN interrupt endpoint. There should be only |
| * one interrupt IN endpoint. |
| */ |
| |
| if ((found & USBHOST_EPINFOUND) != 0) |
| { |
| /* Oops.. more than one endpoint. We don't know |
| * what to do with this. |
| */ |
| |
| return -EINVAL; |
| } |
| |
| found |= USBHOST_EPINFOUND; |
| |
| /* Save the bulk IN endpoint information */ |
| |
| epindesc.hport = hport; |
| epindesc.addr = epdesc->addr & |
| USB_EP_ADDR_NUMBER_MASK; |
| epindesc.in = true; |
| epindesc.xfrtype = USB_EP_ATTR_XFER_INT; |
| epindesc.interval = epdesc->interval; |
| epindesc.mxpacketsize = |
| usbhost_getle16(epdesc->mxpacketsize); |
| |
| uerr("Interrupt IN EP addr:%d mxpacketsize:%d\n", |
| epindesc.addr, epindesc.mxpacketsize); |
| } |
| } |
| } |
| break; |
| |
| /* Other descriptors are just ignored for now */ |
| |
| default: |
| break; |
| } |
| |
| /* If we found everything we need with this interface, then break out |
| * of the loop early. |
| */ |
| |
| if (found == USBHOST_ALLFOUND) |
| { |
| done = true; |
| } |
| |
| /* Increment the address of the next descriptor */ |
| |
| configdesc += desc->len; |
| remaining -= desc->len; |
| } |
| |
| /* Sanity checking... did we find all of things that we need? */ |
| |
| if (found != USBHOST_ALLFOUND) |
| { |
| uerr("ERROR: Found IF:%s BIN:%s EPOUT:%s\n", |
| (found & USBHOST_IFFOUND) != 0 ? "YES" : "NO", |
| (found & USBHOST_EPINFOUND) != 0 ? "YES" : "NO", |
| (found & USBHOST_EPOUTFOUND) != 0 ? "YES" : "NO"); |
| return -EINVAL; |
| } |
| |
| /* We are good... Allocate the endpoints */ |
| |
| ret = DRVR_EPALLOC(hport->drvr, &epoutdesc, &priv->epout); |
| if (ret < 0) |
| { |
| uerr("ERROR: Failed to allocate Interrupt OUT endpoint\n"); |
| return ret; |
| } |
| |
| ret = DRVR_EPALLOC(hport->drvr, &epindesc, &priv->epin); |
| if (ret < 0) |
| { |
| uerr("ERROR: Failed to allocate Interrupt IN endpoint\n"); |
| DRVR_EPFREE(hport->drvr, priv->epout); |
| return ret; |
| } |
| |
| uinfo("Endpoints allocated\n"); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_devinit |
| * |
| * Description: |
| * The USB device has been successfully connected. This completes the |
| * initialization operations. It is first called after the |
| * configuration descriptor has been received. |
| * |
| * This function is called from the connect() method. This function always |
| * executes on the thread of the caller of connect(). |
| * |
| * Input Parameters: |
| * priv - A reference to the class instance. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static inline int usbhost_devinit(FAR struct usbhost_state_s *priv) |
| { |
| char devname[DEV_NAMELEN]; |
| int ret = OK; |
| |
| /* Set aside a transfer buffer for exclusive use by the class driver */ |
| |
| ret = usbhost_talloc(priv); |
| if (ret < 0) |
| { |
| uerr("ERROR: Failed to allocate transfer buffer\n"); |
| return ret; |
| } |
| |
| /* Increment the reference count. This will prevent usbhost_destroy() from |
| * being called asynchronously if the device is removed. |
| */ |
| |
| priv->crefs++; |
| DEBUGASSERT(priv->crefs == 2); |
| |
| /* Start a worker task to poll the USB device. It would be nice to use |
| * the NuttX worker thread to do this, but this task needs to wait for |
| * events and activities on the worker thread should not involve |
| * significant waiting. Having a dedicated thread is more efficient in |
| * this sense, but requires more memory resources, primarily for the |
| * dedicated stack (CONFIG_XBOXCONTROLLER_STACKSIZE). |
| */ |
| |
| /* The inputs to a task started by kthread_create() are very awkward for |
| * this purpose. They are really designed for command line tasks |
| * (argc/argv). So the following is kludge pass binary data when the |
| * controller poll task is started. |
| * |
| * First, make sure we have exclusive access to g_priv (what is the |
| * likelihood of this being used? About zero, but we protect it anyway). |
| */ |
| |
| ret = nxmutex_lock(&g_lock); |
| if (ret < 0) |
| { |
| usbhost_tfree(priv); |
| goto errout; |
| } |
| |
| g_priv = priv; |
| |
| uinfo("Starting thread\n"); |
| ret = kthread_create("xbox", CONFIG_XBOXCONTROLLER_DEFPRIO, |
| CONFIG_XBOXCONTROLLER_STACKSIZE, |
| usbhost_xboxcontroller_poll, NULL); |
| if (ret < 0) |
| { |
| /* Failed to started the poll thread... probably due to memory |
| * resources. |
| */ |
| |
| nxmutex_unlock(&g_lock); |
| goto errout; |
| } |
| |
| priv->pollpid = (pid_t)ret; |
| |
| /* Now wait for the poll task to get properly initialized */ |
| |
| nxsem_wait_uninterruptible(&g_syncsem); |
| nxmutex_unlock(&g_lock); |
| |
| /* Configure the device */ |
| |
| /* Register the driver */ |
| |
| uinfo("Register block driver\n"); |
| usbhost_mkdevname(priv, devname); |
| ret = register_driver(devname, &g_xboxcontroller_fops, 0666, priv); |
| |
| /* Check if we successfully initialized. We now have to be concerned |
| * about asynchronous modification of crefs because the block |
| * driver has been registered. |
| */ |
| |
| errout: |
| nxmutex_lock(&priv->lock); |
| priv->crefs--; |
| nxmutex_unlock(&priv->lock); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_getle16 |
| * |
| * Description: |
| * Get a (possibly unaligned) 16-bit little endian value. |
| * |
| * Input Parameters: |
| * val - A pointer to the first byte of the little endian value. |
| * |
| * Returned Value: |
| * A uint16_t representing the whole 16-bit integer value |
| * |
| ****************************************************************************/ |
| |
| static inline uint16_t usbhost_getle16(const uint8_t *val) |
| { |
| return (uint16_t)val[1] << 8 | (uint16_t)val[0]; |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_putle16 |
| * |
| * Description: |
| * Put a (possibly unaligned) 16-bit little endian value. |
| * |
| * Input Parameters: |
| * dest - A pointer to the first byte to save the little endian value. |
| * val - The 16-bit value to be saved. |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void usbhost_putle16(uint8_t *dest, uint16_t val) |
| { |
| dest[0] = val & 0xff; /* Little endian means LS byte first in byte stream */ |
| dest[1] = val >> 8; |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_talloc |
| * |
| * Description: |
| * Allocate transfer buffer memory. |
| * |
| * Input Parameters: |
| * priv - A reference to the class instance. |
| * |
| * Returned Value: |
| * On success, zero (OK) is returned. On failure, an negated errno value |
| * is returned to indicate the nature of the failure. |
| * |
| ****************************************************************************/ |
| |
| static inline int usbhost_talloc(FAR struct usbhost_state_s *priv) |
| { |
| FAR struct usbhost_hubport_s *hport; |
| |
| DEBUGASSERT(priv != NULL && priv->usbclass.hport != NULL && |
| priv->tbuffer == NULL); |
| hport = priv->usbclass.hport; |
| |
| return DRVR_ALLOC(hport->drvr, &priv->tbuffer, &priv->tbuflen); |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_tfree |
| * |
| * Description: |
| * Free transfer buffer memory. |
| * |
| * Input Parameters: |
| * priv - A reference to the class instance. |
| * |
| * Returned Value: |
| * On success, zero (OK) is returned. On failure, an negated errno value |
| * is returned to indicate the nature of the failure. |
| * |
| ****************************************************************************/ |
| |
| static inline int usbhost_tfree(FAR struct usbhost_state_s *priv) |
| { |
| FAR struct usbhost_hubport_s *hport; |
| int result = OK; |
| |
| DEBUGASSERT(priv != NULL && priv->usbclass.hport != NULL); |
| |
| if (priv->tbuffer) |
| { |
| hport = priv->usbclass.hport; |
| result = DRVR_FREE(hport->drvr, priv->tbuffer); |
| priv->tbuffer = NULL; |
| priv->tbuflen = 0; |
| } |
| |
| return result; |
| } |
| |
| /**************************************************************************** |
| * struct usbhost_registry_s methods |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: usbhost_create |
| * |
| * Description: |
| * This function implements the create() method of struct |
| * usbhost_registry_s. The create() method is a callback into the class |
| * implementation. It is used to (1) create a new instance of the USB |
| * host class state and to (2) bind a USB host driver "session" to the |
| * class instance. Use of this create() method will support environments |
| * where there may be multiple USB ports and multiple USB devices |
| * simultaneously connected. |
| * |
| * Input Parameters: |
| * hport - The hub hat manages the new class instance. |
| * id - In the case where the device supports multiple base classes, |
| * subclasses, or protocols, this specifies which to configure for. |
| * |
| * Returned Value: |
| * On success, this function will return a non-NULL instance of struct |
| * usbhost_class_s that can be used by the USB host driver to communicate |
| * with the USB host class. NULL is returned on failure; this function |
| * will fail only if the hport input parameter is NULL or if there are |
| * insufficient resources to create another USB host class instance. |
| * |
| ****************************************************************************/ |
| |
| static FAR struct usbhost_class_s * |
| usbhost_create(FAR struct usbhost_hubport_s *hport, |
| FAR const struct usbhost_id_s *id) |
| { |
| FAR struct usbhost_state_s *priv; |
| |
| /* Allocate a USB host class instance */ |
| |
| priv = usbhost_allocclass(); |
| if (priv) |
| { |
| /* Initialize the allocated storage class instance */ |
| |
| memset(priv, 0, sizeof(struct usbhost_state_s)); |
| |
| /* Assign a device number to this class instance */ |
| |
| if (usbhost_allocdevno(priv) == OK) |
| { |
| /* Initialize class method function pointers */ |
| |
| priv->usbclass.hport = hport; |
| priv->usbclass.connect = usbhost_connect; |
| priv->usbclass.disconnected = usbhost_disconnected; |
| |
| /* The initial reference count is 1... One reference is held by the |
| * driver. |
| */ |
| |
| priv->crefs = 1; |
| |
| /* Initialize mutex & semaphores (this works okay in the interrupt |
| * context). |
| */ |
| |
| nxmutex_init(&priv->lock); |
| nxsem_init(&priv->waitsem, 0, 0); |
| |
| /* Return the instance of the USB class driver */ |
| |
| return &priv->usbclass; |
| } |
| } |
| |
| /* An error occurred. Free the allocation and return NULL on all failures */ |
| |
| if (priv) |
| { |
| usbhost_freeclass(priv); |
| } |
| |
| return NULL; |
| } |
| |
| /**************************************************************************** |
| * struct usbhost_class_s methods |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: usbhost_connect |
| * |
| * Description: |
| * This function implements the connect() method of struct |
| * usbhost_class_s. This method is a callback into the class |
| * implementation. It is used to provide the device's configuration |
| * descriptor to the class so that the class may initialize properly |
| * |
| * Input Parameters: |
| * usbclass - The USB host class entry previously obtained from a call to |
| * create(). |
| * configdesc - A pointer to a uint8_t buffer container the configuration |
| * descriptor. |
| * desclen - The length in bytes of the configuration descriptor. |
| * |
| * Returned Value: |
| * On success, zero (OK) is returned. On a failure, a negated errno value |
| * is returned indicating the nature of the failure |
| * |
| * NOTE that the class instance remains valid upon return with a failure. |
| * It is the responsibility of the higher level enumeration logic to call |
| * CLASS_DISCONNECTED to free up the class driver resources. |
| * |
| * Assumptions: |
| * - This function will *not* be called from an interrupt handler. |
| * - If this function returns an error, the USB host controller driver |
| * must call to DISCONNECTED method to recover from the error |
| * |
| ****************************************************************************/ |
| |
| static int usbhost_connect(FAR struct usbhost_class_s *usbclass, |
| FAR const uint8_t *configdesc, int desclen) |
| { |
| FAR struct usbhost_state_s *priv = (FAR struct usbhost_state_s *)usbclass; |
| int ret; |
| |
| DEBUGASSERT(priv != NULL && |
| configdesc != NULL && |
| desclen >= sizeof(struct usb_cfgdesc_s)); |
| |
| /* Parse the configuration descriptor to get the endpoints */ |
| |
| ret = usbhost_cfgdesc(priv, configdesc, desclen); |
| if (ret < 0) |
| { |
| uerr("ERROR: usbhost_cfgdesc() failed: %d\n", ret); |
| } |
| else |
| { |
| /* Now configure the device and register the NuttX driver */ |
| |
| ret = usbhost_devinit(priv); |
| if (ret < 0) |
| { |
| uerr("ERROR: usbhost_devinit() failed: %d\n", ret); |
| } |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_disconnected |
| * |
| * Description: |
| * This function implements the disconnected() method of struct |
| * usbhost_class_s. This method is a callback into the class |
| * implementation. It is used to inform the class that the USB device has |
| * been disconnected. |
| * |
| * Input Parameters: |
| * usbclass - The USB host class entry previously obtained from a call to |
| * create(). |
| * |
| * Returned Value: |
| * On success, zero (OK) is returned. On a failure, a negated errno value |
| * is returned indicating the nature of the failure |
| * |
| * Assumptions: |
| * This function may be called from an interrupt handler. |
| * |
| ****************************************************************************/ |
| |
| static int usbhost_disconnected(FAR struct usbhost_class_s *usbclass) |
| { |
| FAR struct usbhost_state_s *priv = (FAR struct usbhost_state_s *)usbclass; |
| int i; |
| |
| DEBUGASSERT(priv != NULL); |
| |
| /* Set an indication to any users of the device that the device is no |
| * longer available. |
| */ |
| |
| priv->disconnected = true; |
| uinfo("Disconnected\n"); |
| |
| /* Are there a thread(s) waiting for controller data that will never |
| * come? |
| */ |
| |
| for (i = 0; i < priv->nwaiters; i++) |
| { |
| /* Yes.. wake them up */ |
| |
| nxsem_post(&priv->waitsem); |
| } |
| |
| /* Possibilities: |
| * |
| * - Failure occurred before the controller poll task was started |
| * successfully. In this case, the disconnection will have to be |
| * handled on the worker task. |
| * - Failure occurred after the controller poll task was started |
| * successfully. In this case, the disconnection can be performed on |
| * the mouse poll thread. |
| */ |
| |
| if (priv->polling) |
| { |
| /* The polling task is still alive. Signal the mouse polling task. |
| * When that task wakes up, it will decrement the reference count and, |
| * perhaps, destroy the class instance. Then it will exit. |
| */ |
| |
| nxsig_kill(priv->pollpid, SIGALRM); |
| } |
| else |
| { |
| /* In the case where the failure occurs before the polling task was |
| * started. Now what? We are probably executing from an interrupt |
| * handler here. We will use the worker thread. This is kind of |
| * wasteful and begs for a re-design. |
| */ |
| |
| DEBUGASSERT(priv->work.worker == NULL); |
| work_queue(HPWORK, &priv->work, usbhost_destroy, priv, 0); |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Character driver methods |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: usbhost_open |
| * |
| * Description: |
| * Standard character driver open method. |
| * |
| ****************************************************************************/ |
| |
| static int usbhost_open(FAR struct file *filep) |
| { |
| FAR struct inode *inode; |
| FAR struct usbhost_state_s *priv; |
| irqstate_t flags; |
| int ret; |
| |
| uinfo("Entry\n"); |
| inode = filep->f_inode; |
| priv = inode->i_private; |
| |
| /* Make sure that we have exclusive access to the private data structure */ |
| |
| DEBUGASSERT(priv && priv->crefs > 0 && priv->crefs < USBHOST_MAX_CREFS); |
| ret = nxmutex_lock(&priv->lock); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| /* Check if the controller device is still connected. We need to disable |
| * interrupts momentarily to assure that there are no asynchronous |
| * disconnect events. |
| */ |
| |
| flags = enter_critical_section(); |
| if (priv->disconnected) |
| { |
| /* No... the driver is no longer bound to the class. That means that |
| * the USB storage device is no longer connected. Refuse any further |
| * attempts to open the driver. |
| */ |
| |
| ret = -ENODEV; |
| } |
| else |
| { |
| /* Was the driver previously open? We need to perform special |
| * initialization on the first time that the driver is opened. |
| */ |
| |
| if (!priv->open) |
| { |
| /* Set the thresholding values so that the first button press |
| * will be reported. |
| */ |
| |
| #ifdef NEVER |
| priv->xlast = INVALID_POSITION_B16; |
| priv->ylast = INVALID_POSITION_B16; |
| #ifdef CONFIG_INPUT_MOUSE_WHEEL |
| priv->wlast = INVALID_POSITION_B16; |
| #endif |
| /* Set the reported position to the center of the range */ |
| |
| priv->xaccum = (HIDMOUSE_XMAX_B16 >> 1); |
| priv->yaccum = (HIDMOUSE_YMAX_B16 >> 1); |
| #endif |
| } |
| |
| /* Otherwise, just increment the reference count on the driver */ |
| |
| priv->crefs++; |
| priv->open = true; |
| ret = OK; |
| } |
| |
| leave_critical_section(flags); |
| nxmutex_unlock(&priv->lock); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_close |
| * |
| * Description: |
| * Standard character driver close method. |
| * |
| ****************************************************************************/ |
| |
| static int usbhost_close(FAR struct file *filep) |
| { |
| FAR struct inode *inode; |
| FAR struct usbhost_state_s *priv; |
| irqstate_t flags; |
| int ret; |
| |
| uinfo("Entry\n"); |
| inode = filep->f_inode; |
| priv = inode->i_private; |
| |
| /* Decrement the reference count on the driver */ |
| |
| DEBUGASSERT(priv->crefs >= 1); |
| ret = nxmutex_lock(&priv->lock); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| /* We need to disable interrupts momentarily to assure that there are no |
| * asynchronous poll or disconnect events. |
| */ |
| |
| flags = enter_critical_section(); |
| priv->crefs--; |
| |
| /* Check if the USB controller device is still connected. If the device is |
| * no longer connected, then unregister the driver and free the driver |
| * class instance. |
| */ |
| |
| if (priv->disconnected) |
| { |
| /* If the reference count is one or less then there are two |
| * possibilities: |
| * |
| * 1) It might be zero meaning that the polling thread has already |
| * exited and decremented its count. |
| * 2) If might be one meaning either that (a) the polling thread is |
| * still running and still holds a count, or (b) the polling thread |
| * has exited, but there is still an outstanding open reference. |
| */ |
| |
| if (priv->crefs == 0 || (priv->crefs == 1 && priv->polling)) |
| { |
| /* Yes.. In either case, then the driver is no longer open */ |
| |
| priv->open = false; |
| |
| /* Check if the USB keyboard device is still connected. */ |
| |
| if (priv->crefs == 0) |
| { |
| /* The polling thread is no longer running */ |
| |
| DEBUGASSERT(!priv->polling); |
| |
| /* If the device is no longer connected, unregister the driver |
| * and free the driver class instance. |
| */ |
| |
| usbhost_destroy(priv); |
| |
| /* Skip giving the semaphore... it is no longer valid */ |
| |
| leave_critical_section(flags); |
| return OK; |
| } |
| else /* if (priv->crefs == 1) */ |
| { |
| /* The polling thread is still running. Signal it so that it |
| * will wake up and call usbhost_destroy(). The particular |
| * signal that we use does not matter in this case. |
| */ |
| |
| nxsig_kill(priv->pollpid, SIGALRM); |
| } |
| } |
| } |
| |
| nxmutex_unlock(&priv->lock); |
| leave_critical_section(flags); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_read |
| * |
| * Description: |
| * Standard character driver read method. |
| * |
| ****************************************************************************/ |
| |
| static ssize_t usbhost_read(FAR struct file *filep, FAR char *buffer, |
| size_t len) |
| { |
| FAR struct inode *inode; |
| FAR struct usbhost_state_s *priv; |
| struct xbox_controller_buttonstate_s sample; |
| int ret; |
| |
| DEBUGASSERT(buffer); |
| inode = filep->f_inode; |
| priv = inode->i_private; |
| |
| /* Make sure that we have exclusive access to the private data structure */ |
| |
| DEBUGASSERT(priv && priv->crefs > 0 && priv->crefs < USBHOST_MAX_CREFS); |
| ret = nxmutex_lock(&priv->lock); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| /* Check if the controller is still connected. We need to disable |
| * interrupts momentarily to assure that there are no asynchronous |
| * disconnect events. |
| */ |
| |
| if (priv->disconnected) |
| { |
| /* No... the driver is no longer bound to the class. That means that |
| * the USB controller is no longer connected. Refuse any further |
| * attempts to access the driver. |
| */ |
| |
| ret = -ENODEV; |
| goto errout; |
| } |
| |
| /* Try to read sample data. */ |
| |
| ret = usbhost_sample(priv, &sample); |
| if (ret < 0) |
| { |
| /* Sample data is not available now. We would ave to wait to get |
| * receive sample data. If the user has specified the O_NONBLOCK |
| * option, then just return an error. |
| */ |
| |
| if (filep->f_oflags & O_NONBLOCK) |
| { |
| /* Yes.. then return a failure */ |
| |
| ret = -EAGAIN; |
| goto errout; |
| } |
| |
| /* Wait for sample data */ |
| |
| ret = usbhost_waitsample(priv, &sample); |
| ret = 0; |
| if (ret < 0) |
| { |
| /* We might have been awakened by a signal */ |
| |
| ierr("ERROR: usbhost_waitsample: %d\n", ret); |
| goto errout; |
| } |
| } |
| |
| /* We now have sampled controller data that we can report to the caller. */ |
| |
| memcpy(buffer, &sample, sizeof(struct xbox_controller_buttonstate_s)); |
| |
| ret = sizeof(struct xbox_controller_buttonstate_s); |
| |
| errout: |
| nxmutex_unlock(&priv->lock); |
| iinfo("Returning: %d\n", ret); |
| return (ssize_t)ret; |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_write |
| * |
| * Description: |
| * Standard character driver write method. |
| * |
| ****************************************************************************/ |
| |
| static ssize_t usbhost_write(FAR struct file *filep, FAR const char *buffer, |
| size_t len) |
| { |
| /* Not implemented. */ |
| |
| return -ENOSYS; |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_ioctl |
| * |
| * Description: |
| * Standard character driver ioctl method. |
| * |
| ****************************************************************************/ |
| |
| static int usbhost_ioctl(FAR struct file *filep, int cmd, unsigned long arg) |
| { |
| FAR struct inode *inode; |
| FAR struct usbhost_state_s *priv; |
| int ret = 0; |
| int nbytes; |
| FAR struct usbhost_hubport_s *hport; |
| static uint8_t rumble_cmd[] = |
| { |
| 0x09, 0x00, 0x00, 0x09, 0x00, 0x0f, 0x00, |
| 0x00, 0x00, 0x00, 0xff, 0x00, 0xff |
| }; |
| |
| uinfo("Entered\n"); |
| DEBUGASSERT(buffer); |
| inode = filep->f_inode; |
| priv = inode->i_private; |
| hport = priv->usbclass.hport; |
| |
| /* Check if the controller is still connected. We need to disable |
| * interrupts momentarily to assure that there are no asynchronous |
| * disconnect events. |
| */ |
| |
| if (priv->disconnected) |
| { |
| /* No... the driver is no longer bound to the class. That means that |
| * the USB controller is no longer connected. Refuse any further |
| * attempts to access the driver. |
| */ |
| |
| ret = -ENODEV; |
| goto errout; |
| } |
| |
| /* Determine which IOCTL command to execute. */ |
| |
| switch (cmd) |
| { |
| case XBOX_CONTROLLER_IOCTL_RUMBLE: |
| |
| /* The least significant byte is the weak actuator strength. |
| * The second byte is the strong actuator strength. |
| */ |
| |
| memcpy(priv->obuffer, rumble_cmd, sizeof(rumble_cmd)); |
| priv->obuffer[2] = priv->out_seq_num++; |
| priv->obuffer[8] = (arg >> 1) & 0xff; /* Strong (left actuator) */ |
| priv->obuffer[9] = arg & 0xff; /* Weak (right actuator) */ |
| |
| /* Perform the transfer. */ |
| |
| nbytes = DRVR_TRANSFER(hport->drvr, priv->epout, |
| priv->obuffer, sizeof(rumble_cmd)); |
| |
| /* Did we encounter an error? */ |
| |
| if (nbytes < 0) |
| { |
| ret = nbytes; |
| } |
| |
| break; |
| |
| default: |
| |
| ret = -ENOTTY; |
| goto errout; |
| } |
| |
| errout: |
| iinfo("Returning: %d\n", ret); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: usbhost_poll |
| * |
| * Description: |
| * Standard character driver poll method. |
| * |
| ****************************************************************************/ |
| |
| static int usbhost_poll(FAR struct file *filep, FAR struct pollfd *fds, |
| bool setup) |
| { |
| FAR struct inode *inode; |
| FAR struct usbhost_state_s *priv; |
| int ret; |
| int i; |
| |
| DEBUGASSERT(fds); |
| inode = filep->f_inode; |
| priv = inode->i_private; |
| |
| /* Make sure that we have exclusive access to the private data structure */ |
| |
| DEBUGASSERT(priv); |
| ret = nxmutex_lock(&priv->lock); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| /* Check if the controller is still connected. We need to disable |
| * interrupts momentarily to assure that there are no asynchronous |
| * disconnect events. |
| */ |
| |
| if (priv->disconnected) |
| { |
| /* No... the driver is no longer bound to the class. That means that |
| * the USB controller is no longer connected. Refuse any further |
| * attempts to access the driver. |
| */ |
| |
| ret = -ENODEV; |
| } |
| else if (setup) |
| { |
| /* This is a request to set up the poll. Find an available slot for |
| * the poll structure reference |
| */ |
| |
| for (i = 0; i < CONFIG_XBOXCONTROLLER_NPOLLWAITERS; i++) |
| { |
| /* Find an available slot */ |
| |
| if (!priv->fds[i]) |
| { |
| /* Bind the poll structure and this slot */ |
| |
| priv->fds[i] = fds; |
| fds->priv = &priv->fds[i]; |
| break; |
| } |
| } |
| |
| if (i >= CONFIG_XBOXCONTROLLER_NPOLLWAITERS) |
| { |
| fds->priv = NULL; |
| ret = -EBUSY; |
| goto errout; |
| } |
| |
| /* Should we immediately notify on any of the requested events? Notify |
| * the POLLIN event if there is buffered controller data. |
| */ |
| |
| if (priv->valid) |
| { |
| poll_notify(&fds, 1, POLLIN); |
| } |
| } |
| else |
| { |
| /* This is a request to tear down the poll. */ |
| |
| struct pollfd **slot = (struct pollfd **)fds->priv; |
| DEBUGASSERT(slot); |
| |
| /* Remove all memory of the poll setup */ |
| |
| *slot = NULL; |
| fds->priv = NULL; |
| } |
| |
| errout: |
| nxmutex_unlock(&priv->lock); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: usbhost_xboxcontroller_init |
| * |
| * Description: |
| * Initialize the USB class driver. This function should be called |
| * be platform-specific code in order to initialize and register support |
| * for the USB host class device. |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * On success this function will return zero (OK); A negated errno value |
| * will be returned on failure. |
| * |
| ****************************************************************************/ |
| |
| int usbhost_xboxcontroller_init(void) |
| { |
| /* Advertise our availability to support (certain) devices */ |
| |
| return usbhost_registerclass(&g_xboxcontroller); |
| } |