static void *i2400m_tx_fifo_push(struct i2400m *i2400m, size_t size, size_t padding, bool try_head) { struct device *dev = i2400m_dev(i2400m); size_t room, tail_room, needed_size; void *ptr; needed_size = size + padding; room = I2400M_TX_BUF_SIZE - (i2400m->tx_in - i2400m->tx_out); if (room < needed_size) { d_printf(2, dev, "fifo push %zu/%zu: no space\n", size, padding); return NULL; } tail_room = __i2400m_tx_tail_room(i2400m); if (!try_head && tail_room < needed_size) { if (room - tail_room >= needed_size) { d_printf(2, dev, "fifo push %zu/%zu: tail full\n", size, padding); return TAIL_FULL; } else { d_printf(2, dev, "fifo push %zu/%zu: no head space\n", size, padding); return NULL; } } ptr = i2400m->tx_buf + i2400m->tx_in % I2400M_TX_BUF_SIZE; d_printf(2, dev, "fifo push %zu/%zu: at @%zu\n", size, padding, i2400m->tx_in % I2400M_TX_BUF_SIZE); i2400m->tx_in += size; return ptr; }
static void i2400m_tx_skip_tail(struct i2400m *i2400m) { struct device *dev = i2400m_dev(i2400m); size_t tx_in = i2400m->tx_in % I2400M_TX_BUF_SIZE; size_t tail_room = __i2400m_tx_tail_room(i2400m); struct i2400m_msg_hdr *msg = i2400m->tx_buf + tx_in; if (unlikely(tail_room == 0)) return; BUG_ON(tail_room < sizeof(*msg)); msg->size = tail_room | I2400M_TX_SKIP; d_printf(2, dev, "skip tail: skipping %zu bytes @%zu\n", tail_room, tx_in); i2400m->tx_in += tail_room; }
/* * Allocate @size bytes in the TX fifo, return a pointer to it * * @i2400m: device descriptor * @size: size of the buffer we need to allocate * @padding: ensure that there is at least this many bytes of free * contiguous space in the fifo. This is needed because later on * we might need to add padding. * @try_head: specify either to allocate head room or tail room space * in the TX FIFO. This boolean is required to avoids a system hang * due to an infinite loop caused by i2400m_tx_fifo_push(). * The caller must always try to allocate tail room space first by * calling this routine with try_head = 0. In case if there * is not enough tail room space but there is enough head room space, * (i2400m_tx_fifo_push() returns TAIL_FULL) try to allocate head * room space, by calling this routine again with try_head = 1. * * Returns: * * Pointer to the allocated space. NULL if there is no * space. TAIL_FULL if there is no space at the tail but there is at * the head (Case B below). * * These are the two basic cases we need to keep an eye for -- it is * much better explained in linux/kernel/kfifo.c, but this code * basically does the same. No rocket science here. * * Case A Case B * N ___________ ___________ * | tail room | | data | * | | | | * |<- IN ->| |<- OUT ->| * | | | | * | data | | room | * | | | | * |<- OUT ->| |<- IN ->| * | | | | * | head room | | data | * 0 ----------- ----------- * * We allocate only *contiguous* space. * * We can allocate only from 'room'. In Case B, it is simple; in case * A, we only try from the tail room; if it is not enough, we just * fail and return TAIL_FULL and let the caller figure out if we wants to * skip the tail room and try to allocate from the head. * * There is a corner case, wherein i2400m_tx_new() can get into * an infinite loop calling i2400m_tx_fifo_push(). * In certain situations, tx_in would have reached on the top of TX FIFO * and i2400m_tx_tail_room() returns 0, as described below: * * N ___________ tail room is zero * |<- IN ->| * | | * | | * | | * | data | * |<- OUT ->| * | | * | | * | head room | * 0 ----------- * During such a time, where tail room is zero in the TX FIFO and if there * is a request to add a payload to TX FIFO, which calls: * i2400m_tx() * ->calls i2400m_tx_close() * ->calls i2400m_tx_skip_tail() * goto try_new; * ->calls i2400m_tx_new() * |----> [try_head:] * infinite loop | ->calls i2400m_tx_fifo_push() * | if (tail_room < needed) * | if (head_room => needed) * | return TAIL_FULL; * |<---- goto try_head; * * i2400m_tx() calls i2400m_tx_close() to close the message, since there * is no tail room to accommodate the payload and calls * i2400m_tx_skip_tail() to skip the tail space. Now i2400m_tx() calls * i2400m_tx_new() to allocate space for new message header calling * i2400m_tx_fifo_push() that returns TAIL_FULL, since there is no tail space * to accommodate the message header, but there is enough head space. * The i2400m_tx_new() keeps re-retrying by calling i2400m_tx_fifo_push() * ending up in a loop causing system freeze. * * This corner case is avoided by using a try_head boolean, * as an argument to i2400m_tx_fifo_push(). * * Note: * * Assumes i2400m->tx_lock is taken, and we use that as a barrier * * The indexes keep increasing and we reset them to zero when we * pop data off the queue */ static void *i2400m_tx_fifo_push(struct i2400m *i2400m, size_t size, size_t padding, bool try_head) { struct device *dev = i2400m_dev(i2400m); size_t room, tail_room, needed_size; void *ptr; needed_size = size + padding; room = I2400M_TX_BUF_SIZE - (i2400m->tx_in - i2400m->tx_out); if (room < needed_size) { /* this takes care of Case B */ d_printf(2, dev, "fifo push %zu/%zu: no space\n", size, padding); return NULL; } /* Is there space at the tail? */ tail_room = __i2400m_tx_tail_room(i2400m); if (!try_head && tail_room < needed_size) { /* * If the tail room space is not enough to push the message * in the TX FIFO, then there are two possibilities: * 1. There is enough head room space to accommodate * this message in the TX FIFO. * 2. There is not enough space in the head room and * in tail room of the TX FIFO to accommodate the message. * In the case (1), return TAIL_FULL so that the caller * can figure out, if the caller wants to push the message * into the head room space. * In the case (2), return NULL, indicating that the TX FIFO * cannot accommodate the message. */ if (room - tail_room >= needed_size) { d_printf(2, dev, "fifo push %zu/%zu: tail full\n", size, padding); return TAIL_FULL; /* There might be head space */ } else { d_printf(2, dev, "fifo push %zu/%zu: no head space\n", size, padding); return NULL; /* There is no space */ } } ptr = i2400m->tx_buf + i2400m->tx_in % I2400M_TX_BUF_SIZE; d_printf(2, dev, "fifo push %zu/%zu: at @%zu\n", size, padding, i2400m->tx_in % I2400M_TX_BUF_SIZE); i2400m->tx_in += size; return ptr; }