]> asedeno.scripts.mit.edu Git - linux.git/commitdiff
greybus: uart: Add credits based tracking for transmit path
authorAxel Haslam <ahaslam@baylibre.com>
Tue, 31 May 2016 12:36:11 +0000 (14:36 +0200)
committerGreg Kroah-Hartman <gregkh@google.com>
Wed, 1 Jun 2016 00:18:18 +0000 (17:18 -0700)
To avoid polling the firmware for space, a credit based system
is implemented.

The host will keep track of how many credits (bytes) it has
sent to the firmware, and stop sending data when the quota
is filled.

The host will be informed that the firmware has more
room for data when it handles the receive_credits request
message from the firmware, and will continue to write data
as credits become available.

The firmware and the host may implement an algorithm to aggregate
credits, and avoid extra greybus traffic.

Suggested-by: Johan Hovold <johan@hovoldconsulting.com>
Signed-off-by: Axel Haslam <ahaslam@baylibre.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
drivers/staging/greybus/greybus_protocols.h
drivers/staging/greybus/uart.c

index 072e5c99a93485d31d717e625a91b6cb15d05607..089751c24117efaa03056047bf9a0dea75cfb78b 100644 (file)
@@ -1269,6 +1269,7 @@ struct gb_raw_send_request {
 #define GB_UART_TYPE_SET_CONTROL_LINE_STATE    0x05
 #define GB_UART_TYPE_SEND_BREAK                        0x06
 #define GB_UART_TYPE_SERIAL_STATE              0x07    /* Unsolicited data */
+#define GB_UART_TYPE_RECEIVE_CREDITS           0x08
 
 /* Represents data from AP -> Module */
 struct gb_uart_send_data_request {
@@ -1289,6 +1290,10 @@ struct gb_uart_recv_data_request {
        __u8    data[0];
 } __packed;
 
+struct gb_uart_receive_credits_request {
+       __le16  count;
+} __packed;
+
 struct gb_uart_set_line_coding_request {
        __le32  rate;
        __u8    format;
index 09fae9ae22bbd803735508e4fd450079bb95a49a..14b3e9d06e9c15d328e10596272177cc77edbfbb 100644 (file)
@@ -38,6 +38,7 @@
 
 #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;
@@ -69,6 +70,7 @@ struct gb_tty {
        struct work_struct tx_work;
        struct kfifo write_fifo;
        bool close_pending;
+       unsigned int credits;
 };
 
 static struct tty_driver *gb_tty_driver;
@@ -151,6 +153,53 @@ static int gb_uart_serial_state_handler(struct gb_operation *op)
        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;
@@ -165,6 +214,9 @@ static int gb_uart_request_handler(struct gb_operation *op)
        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);
@@ -190,14 +242,19 @@ static void  gb_uart_tx_write_work(struct work_struct *work)
                        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);
@@ -208,6 +265,9 @@ static void  gb_uart_tx_write_work(struct work_struct *work)
                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;
@@ -386,6 +446,8 @@ static int gb_tty_chars_in_buffer(struct tty_struct *tty)
 
        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;
@@ -764,6 +826,8 @@ static int gb_uart_probe(struct gbphy_device *gbphy_dev,
        if (retval)
                goto exit_buf_free;
 
+       gb_tty->credits = GB_UART_FIRMWARE_CREDITS;
+
        minor = alloc_minor(gb_tty);
        if (minor < 0) {
                if (minor == -ENOSPC) {