]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
ice: add write functionality for GNSS TTY
authorKarol Kolacinski <karol.kolacinski@intel.com>
Fri, 24 Jun 2022 15:22:03 +0000 (17:22 +0200)
committerTony Nguyen <anthony.l.nguyen@intel.com>
Thu, 21 Jul 2022 20:25:17 +0000 (13:25 -0700)
Add the possibility to write raw bytes to the GNSS module through the
first TTY device. This allows user to configure the module.

Create a second read-only TTY device.

Signed-off-by: Karol Kolacinski <karol.kolacinski@intel.com>
Tested-by: Gurucharan <gurucharanx.g@intel.com> (A Contingent worker at Intel)
Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com>
Documentation/networking/device_drivers/ethernet/intel/ice.rst
drivers/net/ethernet/intel/ice/ice.h
drivers/net/ethernet/intel/ice/ice_gnss.c
drivers/net/ethernet/intel/ice/ice_gnss.h

index 67b7a701ce9e15a0bde78072569f1c31d7959295..dc2e60ced927c378b4c91b1eadb52085e5c7d46e 100644 (file)
@@ -901,6 +901,15 @@ To enable/disable UDP Segmentation Offload, issue the following command::
 
   # ethtool -K <ethX> tx-udp-segmentation [off|on]
 
+GNSS module
+-----------
+Allows user to read messages from the GNSS module and write supported commands.
+If the module is physically present, driver creates 2 TTYs for each supported
+device in /dev, ttyGNSS_<device>:<function>_0 and _1. First one (_0) is RW and
+the second one is RO.
+The protocol of write commands is dependent on the GNSS module as the driver
+writes raw bytes from the TTY to the GNSS i2c. Please refer to the module
+documentation for details.
 
 Performance Optimization
 ========================
index f72c5cc4e035d35df3549231f57c232a850d3c6d..1a2e54dbc5a1e9330d187d14624f5aee28baafc6 100644 (file)
@@ -545,8 +545,8 @@ struct ice_pf {
        u32 msg_enable;
        struct ice_ptp ptp;
        struct tty_driver *ice_gnss_tty_driver;
-       struct tty_port gnss_tty_port;
-       struct gnss_serial *gnss_serial;
+       struct tty_port *gnss_tty_port[ICE_GNSS_TTY_MINOR_DEVICES];
+       struct gnss_serial *gnss_serial[ICE_GNSS_TTY_MINOR_DEVICES];
        u16 num_rdma_msix;              /* Total MSIX vectors for RDMA driver */
        u16 rdma_base_vector;
 
index c6d755f707aa80a248cdf51a227cbc5fdcdfa037..b5a7f246d230fdc8aad9b5fe6e6c95acdeadfd53 100644 (file)
 // SPDX-License-Identifier: GPL-2.0
-/* Copyright (C) 2018-2021, Intel Corporation. */
+/* Copyright (C) 2021-2022, Intel Corporation. */
 
 #include "ice.h"
 #include "ice_lib.h"
 #include <linux/tty_driver.h>
 
+/**
+ * ice_gnss_do_write - Write data to internal GNSS
+ * @pf: board private structure
+ * @buf: command buffer
+ * @size: command buffer size
+ *
+ * Write UBX command data to the GNSS receiver
+ */
+static unsigned int
+ice_gnss_do_write(struct ice_pf *pf, unsigned char *buf, unsigned int size)
+{
+       struct ice_aqc_link_topo_addr link_topo;
+       struct ice_hw *hw = &pf->hw;
+       unsigned int offset = 0;
+       int err = 0;
+
+       memset(&link_topo, 0, sizeof(struct ice_aqc_link_topo_addr));
+       link_topo.topo_params.index = ICE_E810T_GNSS_I2C_BUS;
+       link_topo.topo_params.node_type_ctx |=
+               FIELD_PREP(ICE_AQC_LINK_TOPO_NODE_CTX_M,
+                          ICE_AQC_LINK_TOPO_NODE_CTX_OVERRIDE);
+
+       /* It's not possible to write a single byte to u-blox.
+        * Write all bytes in a loop until there are 6 or less bytes left. If
+        * there are exactly 6 bytes left, the last write would be only a byte.
+        * In this case, do 4+2 bytes writes instead of 5+1. Otherwise, do the
+        * last 2 to 5 bytes write.
+        */
+       while (size - offset > ICE_GNSS_UBX_WRITE_BYTES + 1) {
+               err = ice_aq_write_i2c(hw, link_topo, ICE_GNSS_UBX_I2C_BUS_ADDR,
+                                      cpu_to_le16(buf[offset]),
+                                      ICE_MAX_I2C_WRITE_BYTES,
+                                      &buf[offset + 1], NULL);
+               if (err)
+                       goto err_out;
+
+               offset += ICE_GNSS_UBX_WRITE_BYTES;
+       }
+
+       /* Single byte would be written. Write 4 bytes instead of 5. */
+       if (size - offset == ICE_GNSS_UBX_WRITE_BYTES + 1) {
+               err = ice_aq_write_i2c(hw, link_topo, ICE_GNSS_UBX_I2C_BUS_ADDR,
+                                      cpu_to_le16(buf[offset]),
+                                      ICE_MAX_I2C_WRITE_BYTES - 1,
+                                      &buf[offset + 1], NULL);
+               if (err)
+                       goto err_out;
+
+               offset += ICE_GNSS_UBX_WRITE_BYTES - 1;
+       }
+
+       /* Do the last write, 2 to 5 bytes. */
+       err = ice_aq_write_i2c(hw, link_topo, ICE_GNSS_UBX_I2C_BUS_ADDR,
+                              cpu_to_le16(buf[offset]), size - offset - 1,
+                              &buf[offset + 1], NULL);
+       if (err)
+               goto err_out;
+
+       return size;
+
+err_out:
+       dev_err(ice_pf_to_dev(pf), "GNSS failed to write, offset=%u, size=%u, err=%d\n",
+               offset, size, err);
+
+       return offset;
+}
+
+/**
+ * ice_gnss_write_pending - Write all pending data to internal GNSS
+ * @work: GNSS write work structure
+ */
+static void ice_gnss_write_pending(struct kthread_work *work)
+{
+       struct gnss_serial *gnss = container_of(work, struct gnss_serial,
+                                               write_work);
+       struct ice_pf *pf = gnss->back;
+
+       if (!list_empty(&gnss->queue)) {
+               struct gnss_write_buf *write_buf = NULL;
+               unsigned int bytes;
+
+               write_buf = list_first_entry(&gnss->queue,
+                                            struct gnss_write_buf, queue);
+
+               bytes = ice_gnss_do_write(pf, write_buf->buf, write_buf->size);
+               dev_dbg(ice_pf_to_dev(pf), "%u bytes written to GNSS\n", bytes);
+
+               list_del(&write_buf->queue);
+               kfree(write_buf->buf);
+               kfree(write_buf);
+       }
+}
+
 /**
  * ice_gnss_read - Read data from internal GNSS module
  * @work: GNSS read work structure
@@ -104,8 +197,9 @@ exit:
 /**
  * ice_gnss_struct_init - Initialize GNSS structure for the TTY
  * @pf: Board private structure
+ * @index: TTY device index
  */
-static struct gnss_serial *ice_gnss_struct_init(struct ice_pf *pf)
+static struct gnss_serial *ice_gnss_struct_init(struct ice_pf *pf, int index)
 {
        struct device *dev = ice_pf_to_dev(pf);
        struct kthread_worker *kworker;
@@ -118,9 +212,11 @@ static struct gnss_serial *ice_gnss_struct_init(struct ice_pf *pf)
        mutex_init(&gnss->gnss_mutex);
        gnss->open_count = 0;
        gnss->back = pf;
-       pf->gnss_serial = gnss;
+       pf->gnss_serial[index] = gnss;
 
        kthread_init_delayed_work(&gnss->read_work, ice_gnss_read);
+       INIT_LIST_HEAD(&gnss->queue);
+       kthread_init_work(&gnss->write_work, ice_gnss_write_pending);
        /* Allocate a kworker for handling work required for the GNSS TTY
         * writes.
         */
@@ -156,10 +252,10 @@ static int ice_gnss_tty_open(struct tty_struct *tty, struct file *filp)
        tty->driver_data = NULL;
 
        /* Get the serial object associated with this tty pointer */
-       gnss = pf->gnss_serial;
+       gnss = pf->gnss_serial[tty->index];
        if (!gnss) {
                /* Initialize GNSS struct on the first device open */
-               gnss = ice_gnss_struct_init(pf);
+               gnss = ice_gnss_struct_init(pf, tty->index);
                if (!gnss)
                        return -ENOMEM;
        }
@@ -212,25 +308,100 @@ exit:
 }
 
 /**
- * ice_gnss_tty_write - Dummy TTY write function to avoid kernel panic
+ * ice_gnss_tty_write - Write GNSS data
  * @tty: pointer to the tty_struct
  * @buf: pointer to the user data
- * @cnt: the number of characters that was able to be sent to the hardware (or
- *       queued to be sent at a later time)
+ * @count: the number of characters queued to be sent to the HW
+ *
+ * The write function call is called by the user when there is data to be sent
+ * to the hardware. First the tty core receives the call, and then it passes the
+ * data on to the tty driver's write function. The tty core also tells the tty
+ * driver the size of the data being sent.
+ * If any errors happen during the write call, a negative error value should be
+ * returned instead of the number of characters queued to be written.
  */
 static int
-ice_gnss_tty_write(struct tty_struct *tty, const unsigned char *buf, int cnt)
+ice_gnss_tty_write(struct tty_struct *tty, const unsigned char *buf, int count)
 {
-       return 0;
+       struct gnss_write_buf *write_buf;
+       struct gnss_serial *gnss;
+       unsigned char *cmd_buf;
+       struct ice_pf *pf;
+       int err = count;
+
+       /* We cannot write a single byte using our I2C implementation. */
+       if (count <= 1 || count > ICE_GNSS_TTY_WRITE_BUF)
+               return -EINVAL;
+
+       gnss = tty->driver_data;
+       if (!gnss)
+               return -EFAULT;
+
+       pf = (struct ice_pf *)tty->driver->driver_state;
+       if (!pf)
+               return -EFAULT;
+
+       /* Only allow to write on TTY 0 */
+       if (gnss != pf->gnss_serial[0])
+               return -EIO;
+
+       mutex_lock(&gnss->gnss_mutex);
+
+       if (!gnss->open_count) {
+               err = -EINVAL;
+               goto exit;
+       }
+
+       cmd_buf = kcalloc(count, sizeof(*buf), GFP_KERNEL);
+       if (!cmd_buf) {
+               err = -ENOMEM;
+               goto exit;
+       }
+
+       memcpy(cmd_buf, buf, count);
+
+       /* Send the data out to a hardware port */
+       write_buf = kzalloc(sizeof(*write_buf), GFP_KERNEL);
+       if (!write_buf) {
+               err = -ENOMEM;
+               goto exit;
+       }
+
+       write_buf->buf = cmd_buf;
+       write_buf->size = count;
+       INIT_LIST_HEAD(&write_buf->queue);
+       list_add_tail(&write_buf->queue, &gnss->queue);
+       kthread_queue_work(gnss->kworker, &gnss->write_work);
+exit:
+       mutex_unlock(&gnss->gnss_mutex);
+       return err;
 }
 
 /**
- * ice_gnss_tty_write_room - Dummy TTY write_room function to avoid kernel panic
+ * ice_gnss_tty_write_room - Returns the numbers of characters to be written.
  * @tty: pointer to the tty_struct
+ *
+ * This routine returns the numbers of characters the tty driver will accept
+ * for queuing to be written or 0 if either the TTY is not open or user
+ * tries to write to the TTY other than the first.
  */
 static unsigned int ice_gnss_tty_write_room(struct tty_struct *tty)
 {
-       return 0;
+       struct gnss_serial *gnss = tty->driver_data;
+
+       /* Only allow to write on TTY 0 */
+       if (!gnss || gnss != gnss->back->gnss_serial[0])
+               return 0;
+
+       mutex_lock(&gnss->gnss_mutex);
+
+       if (!gnss->open_count) {
+               mutex_unlock(&gnss->gnss_mutex);
+               return 0;
+       }
+
+       mutex_unlock(&gnss->gnss_mutex);
+       return ICE_GNSS_TTY_WRITE_BUF;
 }
 
 static const struct tty_operations tty_gps_ops = {
@@ -250,11 +421,13 @@ static struct tty_driver *ice_gnss_create_tty_driver(struct ice_pf *pf)
        const int ICE_TTYDRV_NAME_MAX = 14;
        struct tty_driver *tty_driver;
        char *ttydrv_name;
+       unsigned int i;
        int err;
 
-       tty_driver = tty_alloc_driver(1, TTY_DRIVER_REAL_RAW);
+       tty_driver = tty_alloc_driver(ICE_GNSS_TTY_MINOR_DEVICES,
+                                     TTY_DRIVER_REAL_RAW);
        if (IS_ERR(tty_driver)) {
-               dev_err(ice_pf_to_dev(pf), "Failed to allocate memory for GNSS TTY\n");
+               dev_err(dev, "Failed to allocate memory for GNSS TTY\n");
                return NULL;
        }
 
@@ -284,23 +457,32 @@ static struct tty_driver *ice_gnss_create_tty_driver(struct ice_pf *pf)
        tty_driver->driver_state = pf;
        tty_set_operations(tty_driver, &tty_gps_ops);
 
-       pf->gnss_serial = NULL;
+       for (i = 0; i < ICE_GNSS_TTY_MINOR_DEVICES; i++) {
+               pf->gnss_tty_port[i] = kzalloc(sizeof(*pf->gnss_tty_port[i]),
+                                              GFP_KERNEL);
+               pf->gnss_serial[i] = NULL;
 
-       tty_port_init(&pf->gnss_tty_port);
-       tty_port_link_device(&pf->gnss_tty_port, tty_driver, 0);
+               tty_port_init(pf->gnss_tty_port[i]);
+               tty_port_link_device(pf->gnss_tty_port[i], tty_driver, i);
+       }
 
        err = tty_register_driver(tty_driver);
        if (err) {
-               dev_err(ice_pf_to_dev(pf), "Failed to register TTY driver err=%d\n",
-                       err);
+               dev_err(dev, "Failed to register TTY driver err=%d\n", err);
 
-               tty_port_destroy(&pf->gnss_tty_port);
+               for (i = 0; i < ICE_GNSS_TTY_MINOR_DEVICES; i++) {
+                       tty_port_destroy(pf->gnss_tty_port[i]);
+                       kfree(pf->gnss_tty_port[i]);
+               }
                kfree(ttydrv_name);
                tty_driver_kref_put(pf->ice_gnss_tty_driver);
 
                return NULL;
        }
 
+       for (i = 0; i < ICE_GNSS_TTY_MINOR_DEVICES; i++)
+               dev_info(dev, "%s%d registered\n", ttydrv_name, i);
+
        return tty_driver;
 }
 
@@ -328,17 +510,25 @@ void ice_gnss_init(struct ice_pf *pf)
  */
 void ice_gnss_exit(struct ice_pf *pf)
 {
+       unsigned int i;
+
        if (!test_bit(ICE_FLAG_GNSS, pf->flags) || !pf->ice_gnss_tty_driver)
                return;
 
-       tty_port_destroy(&pf->gnss_tty_port);
+       for (i = 0; i < ICE_GNSS_TTY_MINOR_DEVICES; i++) {
+               if (pf->gnss_tty_port[i]) {
+                       tty_port_destroy(pf->gnss_tty_port[i]);
+                       kfree(pf->gnss_tty_port[i]);
+               }
 
-       if (pf->gnss_serial) {
-               struct gnss_serial *gnss = pf->gnss_serial;
+               if (pf->gnss_serial[i]) {
+                       struct gnss_serial *gnss = pf->gnss_serial[i];
 
-               kthread_cancel_delayed_work_sync(&gnss->read_work);
-               kfree(gnss);
-               pf->gnss_serial = NULL;
+                       kthread_cancel_work_sync(&gnss->write_work);
+                       kthread_cancel_delayed_work_sync(&gnss->read_work);
+                       kfree(gnss);
+                       pf->gnss_serial[i] = NULL;
+               }
        }
 
        tty_unregister_driver(pf->ice_gnss_tty_driver);
index 9211adb2372c9d32515f10721c9b23334c1db46a..f454dd1d92858b73725e6c7d1825c8cfacacbc19 100644 (file)
@@ -1,5 +1,5 @@
 /* SPDX-License-Identifier: GPL-2.0 */
-/* Copyright (C) 2018-2021, Intel Corporation. */
+/* Copyright (C) 2021-2022, Intel Corporation. */
 
 #ifndef _ICE_GNSS_H_
 #define _ICE_GNSS_H_
@@ -8,14 +8,34 @@
 #include <linux/tty_flip.h>
 
 #define ICE_E810T_GNSS_I2C_BUS         0x2
+#define ICE_GNSS_TIMER_DELAY_TIME      (HZ / 10) /* 0.1 second per message */
+/* Create 2 minor devices, both using the same GNSS module. First one is RW,
+ * second one RO.
+ */
+#define ICE_GNSS_TTY_MINOR_DEVICES     2
+#define ICE_GNSS_TTY_WRITE_BUF         250
+#define ICE_MAX_I2C_DATA_SIZE          FIELD_MAX(ICE_AQC_I2C_DATA_SIZE_M)
+#define ICE_MAX_I2C_WRITE_BYTES                4
+
+/* u-blox ZED-F9T specific definitions */
 #define ICE_GNSS_UBX_I2C_BUS_ADDR      0x42
 /* Data length register is big endian */
 #define ICE_GNSS_UBX_DATA_LEN_H                0xFD
 #define ICE_GNSS_UBX_DATA_LEN_WIDTH    2
 #define ICE_GNSS_UBX_EMPTY_DATA                0xFF
-#define ICE_GNSS_TIMER_DELAY_TIME      (HZ / 10) /* 0.1 second per message */
-#define ICE_MAX_I2C_DATA_SIZE          FIELD_MAX(ICE_AQC_I2C_DATA_SIZE_M)
+/* For u-blox writes are performed without address so the first byte to write is
+ * passed as I2C addr parameter.
+ */
+#define ICE_GNSS_UBX_WRITE_BYTES       (ICE_MAX_I2C_WRITE_BYTES + 1)
 #define ICE_MAX_UBX_READ_TRIES         255
+#define ICE_MAX_UBX_ACK_READ_TRIES     4095
+
+struct gnss_write_buf {
+       struct list_head queue;
+       unsigned int size;
+       unsigned char *buf;
+};
+
 
 /**
  * struct gnss_serial - data used to initialize GNSS TTY port
@@ -25,6 +45,8 @@
  * @gnss_mutex: gnss_mutex used to protect GNSS serial operations
  * @kworker: kwork thread for handling periodic work
  * @read_work: read_work function for handling GNSS reads
+ * @write_work: write_work function for handling GNSS writes
+ * @queue: write buffers queue
  */
 struct gnss_serial {
        struct ice_pf *back;
@@ -33,6 +55,8 @@ struct gnss_serial {
        struct mutex gnss_mutex; /* protects GNSS serial structure */
        struct kthread_worker *kworker;
        struct kthread_delayed_work read_work;
+       struct kthread_work write_work;
+       struct list_head queue;
 };
 
 #if IS_ENABLED(CONFIG_TTY)