/*
    ChibiOS/RT - Copyright (C) 2006-2007 Giovanni Di Sirio.
    This file is part of ChibiOS/RT.
    ChibiOS/RT is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
    ChibiOS/RT is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see .
*/
/**
 * @addtogroup IOQueues
 * @{
 */
#include 
#ifdef CH_USE_QUEUES
/**
 * Initializes an input queue. A Semaphore is internally initialized
 * and works as a counter of the bytes contained in the queue.
 * @param qp pointer to a \p Queue structure
 * @param buffer pointer to a memory area allocated as queue buffer
 * @param size size of the queue buffer
 * @param inotify pointer to a callback function that is invoked when
 *        some data is read from the Queue. The value can be \p NULL.
 */
void chIQInit(Queue *qp, uint8_t *buffer, size_t size, qnotify_t inotify) {
  qp->q_buffer = qp->q_rdptr = qp->q_wrptr = buffer;
  qp->q_top = buffer + size;
  chSemInit(&qp->q_sem, 0);
  qp->q_notify = inotify;
}
/**
 * Resets an input queue. All the data is lost and the waiting threads
 * resumed.
 * @param qp pointer to a \p Queue structure
 */
void chIQReset(Queue *qp) {
  chSysLock();
  qp->q_rdptr = qp->q_wrptr = qp->q_buffer;
  chSemResetI(&qp->q_sem, 0);
  chSysUnlock();
}
/**
 * Inserts a byte into an input queue.
 * @param qp pointer to a \p Queue structure
 * @param b the byte value to be written
 * @return \p Q_OK if the operation is successful or \p Q_FULL if the queue is
 *         full.
 * @note This function is the lower side endpoint of the Input Queue.
 * @note This function must be called with interrupts disabled or from an
 *       interrupt handler.
 */
msg_t chIQPutI(Queue *qp, uint8_t b) {
  if (chIQIsFull(qp))
    return Q_FULL;
  *qp->q_wrptr++ = b;
  if (qp->q_wrptr >= qp->q_top)
    qp->q_wrptr = qp->q_buffer;
  chSemSignalI(&qp->q_sem);
  return Q_OK;
}
/**
 * Gets a byte from the input queue, if the queue is empty then the
 * calling thread is suspended until a byte arrives in the queue.
 * @param qp pointer to a \p Queue structure
 * @return a byte from the queue or \p Q_RESET if the queue was reset
 */
msg_t chIQGet(Queue *qp) {
  uint8_t b;
  chSysLock();
  if (chSemWaitS(&qp->q_sem) < RDY_OK) {
    chSysUnlock();
    return Q_RESET;
  }
  b = *qp->q_rdptr++;
  if (qp->q_rdptr >= qp->q_top)
    qp->q_rdptr = qp->q_buffer;
  if (qp->q_notify)
    qp->q_notify();
  chSysUnlock();
  return b;
}
#ifdef CH_USE_QUEUES_TIMEOUT
/**
 * Gets a byte from the input queue, if the queue is empty then the
 * calling thread is suspended until a byte arrives in the queue or the
 * specified time expires.
 * @param qp pointer to a \p Queue structure
 * @param time the number of ticks before the operation timouts
 * @return a byte from the queue, \p Q_TIMEOUT if the specified time expired
 *         or \p Q_RESET if the queue was reset
 * @note The function is available only if the \p CH_USE_QUEUES_TIMEOUT
 *       option is enabled in \p chconf.h.
 */
msg_t chIQGetTimeout(Queue *qp, systime_t time) {
  uint8_t b;
  msg_t msg;
  chSysLock();
  if ((msg = chSemWaitTimeoutS(&qp->q_sem, time)) < RDY_OK) {
    chSysUnlock();
    return msg;
  }
  b = *qp->q_rdptr++;
  if (qp->q_rdptr >= qp->q_top)
    qp->q_rdptr = qp->q_buffer;
  if (qp->q_notify)
    qp->q_notify();
  chSysUnlock();
  return b;
}
#endif /* CH_USE_QUEUES_TIMEOUTS */
/**
 * Reads some data from the input queue into the specified buffer. The function
 * is non-blocking and can return zero if the queue is empty.
 * @param qp pointer to a \p Queue structure
 * @param buffer the data buffer
 * @param n the maximum amount of data to be read
 * @return the number of bytes read
 * @note This function is the upper side endpoint of the input queue.
 * @note The function is not atomic, if you need atomicity it is suggested
 *       to use a semaphore for mutual exclusion.
 */
size_t chIQRead(Queue *qp, uint8_t *buffer, size_t n) {
  size_t r = 0;
  while (n--) {
    chSysLock();
    if (chIQIsEmpty(qp)) {
      chSysUnlock();
      break;
    }
    chSemFastWaitS(&qp->q_sem);
    *buffer++ = *qp->q_rdptr++;
    if (qp->q_rdptr >= qp->q_top)
      qp->q_rdptr = qp->q_buffer;
    chSysUnlock();
    r++;
  }
  if (r && qp->q_notify) {
    chSysLock();
    qp->q_notify();
    chSysUnlock();
  }
  return r;
}
/**
 * Initializes an output queue. A Semaphore is internally initialized
 * and works as a counter of the free bytes in the queue.
 * @param qp pointer to a \p Queue structure
 * @param buffer pointer to a memory area allocated as queue buffer
 * @param size size of the queue buffer
 * @param onotify pointer to a callback function that is invoked when
 *        some data is written in the Queue. The value can be \p NULL.
 */
void chOQInit(Queue *qp, uint8_t *buffer, size_t size, qnotify_t onotify) {
  qp->q_buffer = qp->q_rdptr = qp->q_wrptr = buffer;
  qp->q_top = buffer + size;
  chSemInit(&qp->q_sem, size);
  qp->q_notify = onotify;
}
/**
 * Resets an Output Queue. All the data is lost and the waiting threads
 * resumed.
 * @param qp pointer to a \p Queue structure
 */
void chOQReset(Queue *qp) {
  chSysLock();
  qp->q_rdptr = qp->q_wrptr = qp->q_buffer;
  chSemResetI(&qp->q_sem, (cnt_t)(qp->q_top - qp->q_buffer));
  chSysUnlock();
}
/**
 * Inserts a byte in the output queue, if the queue is full then the thread
 * is suspended until the queue has free space available.
 * @param qp pointer to a \p Queue structure
 * @param b the byte value to be written
 */
void chOQPut(Queue *qp, uint8_t b) {
  chSysLock();
  chSemWaitS(&qp->q_sem);
  *qp->q_wrptr++ = b;
  if (qp->q_wrptr >= qp->q_top)
    qp->q_wrptr = qp->q_buffer;
  if (qp->q_notify)
    qp->q_notify();
  chSysUnlock();
}
/**
 * Gets a byte from an output queue.
 * @param qp pointer to a \p Queue structure
 * @return the byte value or \p Q_EMPTY if the queue is empty
 * @note This function is the lower side endpoint of the output queue.
 * @note This function must be called with interrupts disabled or from an
 *       interrupt handler.
 */
msg_t chOQGetI(Queue *qp) {
  uint8_t b;
  if (chOQIsEmpty(qp))
    return Q_EMPTY;
  b = *qp->q_rdptr++;
  if (qp->q_rdptr >= qp->q_top)
    qp->q_rdptr = qp->q_buffer;
  chSemSignalI(&qp->q_sem);
  return b;
}
/**
 * Writes some data from the specified buffer into the queue. The function
 * is non-blocking and can return zero if the queue is full.
 * @param qp pointer to a \p Queue structure
 * @param buffer the data buffer
 * @param n the maximum amount of data to be written
 * @note This function is the upper side endpoint of the output queue.
 * @note The function is not atomic, if you need atomicity it is suggested
 *       to use a semaphore for mutual exclusion.
 */
size_t chOQWrite(Queue *qp, uint8_t *buffer, size_t n) {
  size_t w = 0;
  while (n--) {
    chSysLock();
    if (chOQIsFull(qp)) {
      chSysUnlock();
      break;
    }
    chSemFastWaitS(&qp->q_sem);
    *qp->q_wrptr++ = *buffer++;
    if (qp->q_wrptr >= qp->q_top)
      qp->q_wrptr = qp->q_buffer;
    chSysUnlock();
    w++;
  }
  if (w && qp->q_notify) {
    chSysLock();
    qp->q_notify();
    chSysUnlock();
  }
  return w;
}
#endif  /* CH_USE_QUEUES */
#ifdef CH_USE_QUEUES_HALFDUPLEX
 /**
 * Initializes an half duplex queue.
 * @param qp pointer to the \p HalfDuplexQueue structure
 * @param buffer pointer to a memory area allocated as buffer for the queue
 * @param size the size of the queue buffer
 * @param inotify pointer to a callback function that is invoked when
 *        some data is read from the queue. The value can be \p NULL.
 * @param onotify pointer to a callback function that is invoked when
 *        some data is written to the queue. The value can be \p NULL.
 */
void chHDQInit(HalfDuplexQueue *qp, uint8_t *buffer, size_t size,
               qnotify_t inotify, qnotify_t onotify) {
  qp->hdq_buffer = qp->hdq_rdptr = qp->hdq_wrptr = buffer;
  qp->hdq_top = buffer + size;
  chSemInit(&qp->hdq_isem, 0);
  chSemInit(&qp->hdq_osem, size);
  qp->hdq_inotify = inotify;
  qp->hdq_onotify = onotify;
}
/**
 * Reads a byte from the receive queue, if the queue is empty or is in
 * transmission mode then the invoking thread is suspended.
 * @param qp pointer to a \p HalfDuplexQueue structure
 * @return the byte value or \p Q_RESET if the queue was reset
 */
msg_t chHDQGetReceive(HalfDuplexQueue *qp) {
  uint8_t b;
  chSysLock();
  if (chSemWaitS(&qp->hdq_isem) < RDY_OK) {
    chSysUnlock();
    return Q_RESET;
  }
  /*
   * NOTE: The semaphore can be signaled only if the queue is in
   *       receive mode.
   */
  b = *qp->hdq_rdptr++;
  if (qp->hdq_rdptr >= qp->hdq_top)
    qp->hdq_rdptr = qp->hdq_buffer;
  if (qp->hdq_inotify)
    qp->hdq_inotify();
  chSysUnlock();
  return b;
}
#ifdef CH_USE_QUEUES_TIMEOUT
/**
 * Reads a byte from the receive queue, if the queue is empty or is in
 * transmission mode then the invoking thread is suspended.
 * @param qp pointer to a \p HalfDuplexQueue structure
 * @param time the number of ticks before the operation timouts
 * @return the byte value or \p Q_TIMEOUT if a timeout occurs
 * @note The function is available only if the \p CH_USE_QUEUES_TIMEOUT
 *       option is enabled in \p chconf.h.
 */
msg_t chHDQGetReceiveTimeout(HalfDuplexQueue *qp, systime_t time) {
  uint8_t b;
  msg_t msg;
  chSysLock();
  if ((msg = chSemWaitTimeoutS(&qp->hdq_isem, time)) < RDY_OK) {
    chSysUnlock();
    return msg;
  }
  /*
   * NOTE: The semaphore can be signaled only if the queue is in
   *       receive mode.
   */
  b = *qp->hdq_rdptr++;
  if (qp->hdq_rdptr >= qp->hdq_top)
    qp->hdq_rdptr = qp->hdq_buffer;
  if (qp->hdq_inotify)
    qp->hdq_inotify();
  chSysUnlock();
  return b;
}
#endif
/**
 * Writes a byte into the transmit queue. If the buffer contains unread
 * input data then the the buffer is cleared and the queue goes in
 * transmission mode.
 * @param qp pointer to a \p HalfDuplexQueue structure
 * @param b the byte value to be written
 */
void chHDQPutTransmit(HalfDuplexQueue *qp, uint8_t b) {
  chSysLock();
  /*
   * Transmission mode requires that all the unread data must be destroyed.
   */
  if (qp->hdq_isem.s_cnt > 0) {
    qp->hdq_isem.s_cnt = 0;
    qp->hdq_rdptr = qp->hdq_wrptr = qp->hdq_buffer;
  }
  /*
   * Goes in transmission mode.
   */
  chSemWaitS(&qp->hdq_osem);
  *qp->hdq_wrptr++ = b;
  if (qp->hdq_wrptr >= qp->hdq_top)
    qp->hdq_wrptr = qp->hdq_buffer;
  if (qp->hdq_onotify)
    qp->hdq_onotify();
  chSysUnlock();
}
/**
 * Gets a byte from the transmit queue.
 * @param qp pointer to a \p HalfDuplexQueue structure
 * @return the byte value or \p Q_EMPTY if the transmit queue is empty (not in
 *         transmission mode)
 * @note This function must be called with interrupts disabled or from an
 *       interrupt handler.
 */
msg_t chHDQGetTransmitI(HalfDuplexQueue *qp) {
  uint8_t b;
  if (!chHDQIsTransmitting(qp))
    return Q_EMPTY;
  b = *qp->hdq_rdptr++;
  if (qp->hdq_rdptr >= qp->hdq_top)
    qp->hdq_rdptr = qp->hdq_buffer;
  chSemSignalI(&qp->hdq_osem);
  return b;
}
/**
 * Writes a byte into the receive queue. If the queue is in transmission mode
 * then the byte is lost.
 * @param qp pointer to a \p HalfDuplexQueue structure
 * @param b the byte value to be written
 * @return \p Q_OK if the operation is successful or \p Q_FULL if the driver
 *         is in transmit mode or the receive queue is full.
 * @note This function must be called with interrupts disabled or from an
 *       interrupt handler.
 */
msg_t chHDQPutReceiveI(HalfDuplexQueue *qp, uint8_t b) {
  if (chHDQIsTransmitting(qp))
    return Q_FULL;
  if (chHDQIsFullReceive(qp))
    return Q_FULL;
  *qp->hdq_wrptr++ = b;
  if (qp->hdq_wrptr >= qp->hdq_top)
    qp->hdq_wrptr = qp->hdq_buffer;
  chSemSignalI(&qp->hdq_isem);
  return Q_OK;
}
#endif /* CH_USE_QUEUES_HALFDUPLEX */
/** @} */