#define GB_UART_WRITE_FIFO_SIZE PAGE_SIZE
#define GB_UART_WRITE_ROOM_MARGIN 1 /* leave some space in fifo */
+#define GB_UART_FIRMWARE_CREDITS 4096
struct gb_tty_line_coding {
__le32 rate;
struct work_struct tx_work;
struct kfifo write_fifo;
bool close_pending;
+ unsigned int credits;
};
static struct tty_driver *gb_tty_driver;
return 0;
}
+static int gb_uart_receive_credits_handler(struct gb_operation *op)
+{
+ struct gb_connection *connection = op->connection;
+ struct gb_tty *gb_tty = gb_connection_get_data(connection);
+ struct gb_message *request = op->request;
+ struct gb_uart_receive_credits_request *credit_request;
+ unsigned long flags;
+ unsigned int incoming_credits;
+ int ret = 0;
+
+ if (request->payload_size < sizeof(*credit_request)) {
+ dev_err(&gb_tty->gbphy_dev->dev,
+ "short receive_credits event received (%zu < %zu)\n",
+ request->payload_size,
+ sizeof(*credit_request));
+ return -EINVAL;
+ }
+
+ credit_request = request->payload;
+ incoming_credits = le16_to_cpu(credit_request->count);
+
+ spin_lock_irqsave(&gb_tty->write_lock, flags);
+ gb_tty->credits += incoming_credits;
+ if (gb_tty->credits > GB_UART_FIRMWARE_CREDITS) {
+ gb_tty->credits -= incoming_credits;
+ ret = -EINVAL;
+ }
+ spin_unlock_irqrestore(&gb_tty->write_lock, flags);
+
+ if (ret) {
+ dev_err(&gb_tty->gbphy_dev->dev,
+ "invalid number of incoming credits: %d\n",
+ incoming_credits);
+ return ret;
+ }
+
+ if (!gb_tty->close_pending)
+ schedule_work(&gb_tty->tx_work);
+
+ /*
+ * the port the tty layer may be waiting for credits
+ */
+ tty_port_tty_wakeup(&gb_tty->port);
+
+ return ret;
+}
+
static int gb_uart_request_handler(struct gb_operation *op)
{
struct gb_connection *connection = op->connection;
case GB_UART_TYPE_SERIAL_STATE:
ret = gb_uart_serial_state_handler(op);
break;
+ case GB_UART_TYPE_RECEIVE_CREDITS:
+ ret = gb_uart_receive_credits_handler(op);
+ break;
default:
dev_err(&gb_tty->gbphy_dev->dev,
"unsupported unsolicited request: 0x%02x\n", type);
break;
spin_lock_irqsave(&gb_tty->write_lock, flags);
+ send_size = gb_tty->buffer_payload_max;
+ if (send_size > gb_tty->credits)
+ send_size = gb_tty->credits;
+
send_size = kfifo_out_peek(&gb_tty->write_fifo,
&request->data[0],
- gb_tty->buffer_payload_max);
+ send_size);
if (!send_size) {
spin_unlock_irqrestore(&gb_tty->write_lock, flags);
break;
}
+ gb_tty->credits -= send_size;
spin_unlock_irqrestore(&gb_tty->write_lock, flags);
request->size = cpu_to_le16(send_size);
if (ret) {
dev_err(&gb_tty->gbphy_dev->dev,
"send data error: %d\n", ret);
+ spin_lock_irqsave(&gb_tty->write_lock, flags);
+ gb_tty->credits += send_size;
+ spin_unlock_irqrestore(&gb_tty->write_lock, flags);
if (!gb_tty->close_pending)
schedule_work(work);
return;
spin_lock_irqsave(&gb_tty->write_lock, flags);
chars = kfifo_len(&gb_tty->write_fifo);
+ if (gb_tty->credits < GB_UART_FIRMWARE_CREDITS)
+ chars += GB_UART_FIRMWARE_CREDITS - gb_tty->credits;
spin_unlock_irqrestore(&gb_tty->write_lock, flags);
return chars;
if (retval)
goto exit_buf_free;
+ gb_tty->credits = GB_UART_FIRMWARE_CREDITS;
+
minor = alloc_minor(gb_tty);
if (minor < 0) {
if (minor == -ENOSPC) {