/**
 * @brief   Notification of data inserted into the output queue.
 *
 * @param[in] qp        the queue pointer.
 */
static void onotify(io_queue_t *qp) {
  size_t n;
  SerialUSBDriver *sdup = qGetLink(qp);

  /* If the USB driver is not in the appropriate state then transactions
     must not be started.*/
  if ((usbGetDriverStateI(sdup->config->usbp) != USB_ACTIVE) ||
      (sdup->state != SDU_READY)) {
    return;
  }

  /* If there is not an ongoing transaction and the output queue contains
     data then a new transaction is started.*/
  if (!usbGetTransmitStatusI(sdup->config->usbp, sdup->config->bulk_in)) {
    if ((n = oqGetFullI(&sdup->oqueue)) > 0U) {
      osalSysUnlock();

      usbPrepareQueuedTransmit(sdup->config->usbp,
                               sdup->config->bulk_in,
                               &sdup->oqueue, n);

      osalSysLock();
      (void) usbStartTransmitI(sdup->config->usbp, sdup->config->bulk_in);
    }
  }
}
void usb_debug_flush_output(HIDDebugDriver *hiddp) {
  size_t n;

  /* we'll sleep for a moment to finish any transfers that may be pending already */
  /* there's a race condition somewhere, maybe because we have 2x buffer */
  chThdSleepMilliseconds(2);
  osalSysLock();
  /* check that the states of things are as they're supposed to */
  if((usbGetDriverStateI(hiddp->config->usbp) != USB_ACTIVE) ||
     (hiddp->state != HIDDEBUG_READY)) {
    osalSysUnlock();
    return;
  }

  /* rearm the timer */
  chVTSetI(&hid_debug_flush_timer, MS2ST(DEBUG_TX_FLUSH_MS), hid_debug_flush_cb, hiddp);

  /* don't do anything if the queue is empty */
  if((n = oqGetFullI(&hiddp->oqueue)) == 0) {
    osalSysUnlock();
    return;
  }

  osalSysUnlock();
  /* if we don't have enough bytes in the queue, fill with zeroes */
  while(n++ < DEBUG_TX_SIZE) {
    oqPut(&hiddp->oqueue, 0);
  }
  /* will transmit automatically because of the onotify callback */
  /* which transmits as soon as the queue has enough */
}
/**
 * @brief   Default data transmitted callback.
 * @details The application must use this function as callback for the IN
 *          data endpoint.
 *
 * @param[in] usbp      pointer to the @p USBDriver object
 * @param[in] ep        endpoint number
 */
void hidDebugDataTransmitted(USBDriver *usbp, usbep_t ep) {
  HIDDebugDriver *hiddp = usbp->in_params[ep - 1U];
  size_t n;

  if(hiddp == NULL) {
    return;
  }

  osalSysLockFromISR();

  /* rearm the flush timer */
  chVTSetI(&hid_debug_flush_timer, MS2ST(DEBUG_TX_FLUSH_MS), hid_debug_flush_cb, hiddp);

  /* see if we've transmitted everything */
  if((n = oqGetFullI(&hiddp->oqueue)) == 0) {
    chnAddFlagsI(hiddp, CHN_OUTPUT_EMPTY);
  }

  /* Check if there's enough data in the queue to send again */
  if(n >= DEBUG_TX_SIZE) {
    /* The endpoint cannot be busy, we are in the context of the callback,
     * so it is safe to transmit without a check.*/
    osalSysUnlockFromISR();

    usbPrepareQueuedTransmit(usbp, ep, &hiddp->oqueue, DEBUG_TX_SIZE);

    osalSysLockFromISR();
    (void)usbStartTransmitI(usbp, ep);
  }

  osalSysUnlockFromISR();
}
/**
 * @brief   Notification of data inserted into the output queue.
 *
 * @param[in] qp        the queue pointer.
 */
static void onotify(io_queue_t *qp) {
  size_t n;
  HIDDebugDriver *hiddp = qGetLink(qp);

  /* If the USB driver is not in the appropriate state then transactions
   * must not be started.*/
  if((usbGetDriverStateI(hiddp->config->usbp) != USB_ACTIVE) ||
     (hiddp->state != HIDDEBUG_READY)) {
    return;
  }

  /* If there is not an ongoing transaction and the output queue contains
   * enough data then a new transaction is started.*/
  if(!usbGetTransmitStatusI(hiddp->config->usbp, hiddp->config->ep_in)) {
    if((n = oqGetFullI(&hiddp->oqueue)) >= DEBUG_TX_SIZE) {
      osalSysUnlock();

      usbPrepareQueuedTransmit(hiddp->config->usbp,
                               hiddp->config->ep_in,
                               &hiddp->oqueue, DEBUG_TX_SIZE);

      osalSysLock();
      (void)usbStartTransmitI(hiddp->config->usbp, hiddp->config->ep_in);
    }
  }
}
/**
 * @brief   Default data transmitted callback.
 * @details The application must use this function as callback for the IN
 *          data endpoint.
 *
 * @param[in] usbp      pointer to the @p USBDriver object
 * @param[in] ep        endpoint number
 */
void sduDataTransmitted(USBDriver *usbp, usbep_t ep) {
  size_t n;
  SerialUSBDriver *sdup = usbp->in_params[ep - 1U];

  if (sdup == NULL) {
    return;
  }

  osalSysLockFromISR();
  chnAddFlagsI(sdup, CHN_OUTPUT_EMPTY);

  /*lint -save -e9013 [15.7] There is no else because it is not needed.*/
  if ((n = oqGetFullI(&sdup->oqueue)) > 0U) {
    /* The endpoint cannot be busy, we are in the context of the callback,
       so it is safe to transmit without a check.*/
    osalSysUnlockFromISR();

    usbPrepareQueuedTransmit(usbp, ep, &sdup->oqueue, n);

    osalSysLockFromISR();
    (void) usbStartTransmitI(usbp, ep);
  }
  else if ((usbp->epc[ep]->in_state->txsize > 0U) &&
           ((usbp->epc[ep]->in_state->txsize &
            ((size_t)usbp->epc[ep]->in_maxsize - 1U)) == 0U)) {
    /* Transmit zero sized packet in case the last one has maximum allowed
       size. Otherwise the recipient may expect more data coming soon and
       not return buffered data to app. See section 5.8.3 Bulk Transfer
       Packet Size Constraints of the USB Specification document.*/
    osalSysUnlockFromISR();

    usbPrepareQueuedTransmit(usbp, ep, &sdup->oqueue, 0);

    osalSysLockFromISR();
    (void) usbStartTransmitI(usbp, ep);
  }
  /*lint -restore*/

  osalSysUnlockFromISR();
}
/* callback */
static void hid_debug_flush_cb(void *arg) {
  HIDDebugDriver *hiddp = (HIDDebugDriver *)arg;
  size_t i, n;
  uint8_t buf[DEBUG_TX_SIZE];

  osalSysLockFromISR();

  /* check that the states of things are as they're supposed to */
  if((usbGetDriverStateI(hiddp->config->usbp) != USB_ACTIVE) ||
     (hiddp->state != HIDDEBUG_READY)) {
    /* rearm the timer */
    chVTSetI(&hid_debug_flush_timer, MS2ST(DEBUG_TX_FLUSH_MS), hid_debug_flush_cb, hiddp);
    osalSysUnlockFromISR();
    return;
  }

  /* don't do anything if the queue or has enough stuff in it */
  if(((n = oqGetFullI(&hiddp->oqueue)) == 0) || (n >= DEBUG_TX_SIZE)) {
    /* rearm the timer */
    chVTSetI(&hid_debug_flush_timer, MS2ST(DEBUG_TX_FLUSH_MS), hid_debug_flush_cb, hiddp);
    osalSysUnlockFromISR();
    return;
  }

  /* there's stuff hanging in the queue - so dequeue and send */
  for(i = 0; i < n; i++)
    buf[i] = (uint8_t)oqGetI(&hiddp->oqueue);
  for(i = n; i < DEBUG_TX_SIZE; i++)
    buf[i] = 0;
  osalSysUnlockFromISR();
  usbPrepareTransmit(hiddp->config->usbp, hiddp->config->ep_in, buf, DEBUG_TX_SIZE);
  osalSysLockFromISR();
  (void)usbStartTransmitI(hiddp->config->usbp, hiddp->config->ep_in);

  /* rearm the timer */
  chVTSetI(&hid_debug_flush_timer, MS2ST(DEBUG_TX_FLUSH_MS), hid_debug_flush_cb, hiddp);
  osalSysUnlockFromISR();
}