ice: add write functionality for GNSS TTY

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>
This commit is contained in:
Karol Kolacinski 2022-06-24 17:22:03 +02:00 committed by Tony Nguyen
parent fcf9b695a5
commit d6b98c8d24
4 changed files with 254 additions and 31 deletions

View 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
========================

View 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;

View File

@ -1,10 +1,103 @@
// 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)
{
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_work_sync(&gnss->write_work);
kthread_cancel_delayed_work_sync(&gnss->read_work);
kfree(gnss);
pf->gnss_serial = NULL;
pf->gnss_serial[i] = NULL;
}
}
tty_unregister_driver(pf->ice_gnss_tty_driver);

View 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)