summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPixelBot AutoMerger <android-nexus-securitybot@system.gserviceaccount.com>2023-12-17 18:43:17 -0800
committerSecurityBot <android-nexus-securitybot@system.gserviceaccount.com>2023-12-17 18:43:17 -0800
commit4eb15b2a3a2c4ff1b7b8c02168b22bab8958ffa3 (patch)
treecdecea5622708ec3a4564da97ebefe9037a00741
parent45dd856fa22e9a89259e38aecf1c6dfde5450a39 (diff)
parent3a481043276aab160b88fe1ace34598df43945c8 (diff)
downloadlwis-4eb15b2a3a2c4ff1b7b8c02168b22bab8958ffa3.tar.gz
Merge android13-gs-pixel-5.10-24Q1 into android13-gs-pixel-5.10-24Q2
SBMerger: 571992243 Change-Id: I01211eaeefe9ed08293cabdcc730dac853142f7a Signed-off-by: SecurityBot <android-nexus-securitybot@system.gserviceaccount.com>
-rw-r--r--Kbuild2
-rw-r--r--lwis_device.c27
-rw-r--r--lwis_device.h21
-rw-r--r--lwis_device_i2c.c23
-rw-r--r--lwis_device_i2c.h4
-rw-r--r--lwis_device_top.c4
-rw-r--r--lwis_dt.c48
-rw-r--r--lwis_i2c.c21
-rw-r--r--lwis_i2c_bus_manager.c751
-rw-r--r--lwis_i2c_bus_manager.h118
-rw-r--r--lwis_i2c_sched.c62
-rw-r--r--lwis_i2c_sched.h33
-rw-r--r--lwis_ioctl.c19
-rw-r--r--lwis_periodic_io.c68
-rw-r--r--lwis_transaction.c308
-rw-r--r--lwis_transaction.h15
-rw-r--r--lwis_util.c5
-rw-r--r--lwis_util.h5
18 files changed, 1443 insertions, 91 deletions
diff --git a/Kbuild b/Kbuild
index 7dcb1d9..37d3f3a 100644
--- a/Kbuild
+++ b/Kbuild
@@ -24,6 +24,8 @@ lwis-objs += lwis_io_entry.o
lwis-objs += lwis_allocator.o
lwis-objs += lwis_version.o
lwis-objs += lwis_fence.o
+lwis-objs += lwis_i2c_bus_manager.o
+lwis-objs += lwis_i2c_sched.o
# Anchorage specific files
ifeq ($(CONFIG_SOC_GS101), y)
diff --git a/lwis_device.c b/lwis_device.c
index 1ff65b2..6017095 100644
--- a/lwis_device.c
+++ b/lwis_device.c
@@ -41,6 +41,7 @@
#include "lwis_util.h"
#include "lwis_version.h"
#include "lwis_trace.h"
+#include "lwis_i2c_bus_manager.h"
#ifdef CONFIG_OF
#include "lwis_dt.h"
@@ -84,6 +85,13 @@ static void transaction_work_func(struct kthread_work *work)
lwis_process_worker_queue(client);
}
+static void i2c_process_work_func(struct kthread_work *work)
+{
+ /* i2c_work stores the context of the lwis_client submitting the transfer request */
+ struct lwis_client *client = container_of(work, struct lwis_client, i2c_work);
+ lwis_i2c_bus_manager_process_worker_queue(client);
+}
+
/*
* lwis_open: Opening an instance of a LWIS device
*/
@@ -114,6 +122,7 @@ static int lwis_open(struct inode *node, struct file *fp)
mutex_init(&lwis_client->lock);
spin_lock_init(&lwis_client->periodic_io_lock);
spin_lock_init(&lwis_client->event_lock);
+ spin_lock_init(&lwis_client->flush_lock);
/* Empty hash table for client event states */
hash_init(lwis_client->event_states);
@@ -135,6 +144,7 @@ static int lwis_open(struct inode *node, struct file *fp)
lwis_allocator_init(lwis_dev);
kthread_init_work(&lwis_client->transaction_work, transaction_work_func);
+ kthread_init_work(&lwis_client->i2c_work, i2c_process_work_func);
/* Start transaction processor task */
lwis_transaction_init(lwis_client);
@@ -151,6 +161,15 @@ static int lwis_open(struct inode *node, struct file *fp)
/* Storing the client handle in fp private_data for easy access */
fp->private_data = lwis_client;
+ spin_lock_irqsave(&lwis_client->flush_lock, flags);
+ lwis_client->flush_state = NOT_FLUSHING;
+ spin_unlock_irqrestore(&lwis_client->flush_lock, flags);
+
+ if (lwis_i2c_bus_manager_connect_client(lwis_client)) {
+ dev_err(lwis_dev->dev, "Failed to connect lwis client to I2C bus manager\n");
+ return -EINVAL;
+ }
+
lwis_client->is_enabled = false;
return 0;
}
@@ -210,6 +229,7 @@ static int lwis_release_client(struct lwis_client *lwis_client)
struct lwis_device *lwis_dev = lwis_client->lwis_dev;
int rc = 0;
unsigned long flags;
+
rc = lwis_cleanup_client(lwis_client);
if (rc) {
return rc;
@@ -225,7 +245,10 @@ static int lwis_release_client(struct lwis_client *lwis_client)
}
spin_unlock_irqrestore(&lwis_dev->lock, flags);
+ lwis_i2c_bus_manager_disconnect_client(lwis_client);
+
kfree(lwis_client);
+
return 0;
}
@@ -1588,6 +1611,10 @@ void lwis_base_unprobe(struct lwis_device *unprobe_lwis_dev)
&lwis_dev->plat_dev->dev);
lwis_dev->irq_gpios_info.gpios = NULL;
}
+
+ /* Disconnect from the bus manager */
+ lwis_i2c_bus_manager_disconnect(lwis_dev);
+
/* Destroy device */
if (!IS_ERR_OR_NULL(lwis_dev->dev)) {
device_destroy(core.dev_class,
diff --git a/lwis_device.h b/lwis_device.h
index c011775..1313587 100644
--- a/lwis_device.h
+++ b/lwis_device.h
@@ -47,6 +47,19 @@
#define BTS_UNSUPPORTED -1
#define MAX_UNIFIED_POWER_DEVICE 8
+/* enum lwis_client_flush_state
+ * Client flush states indicate if the client has been issued a
+ * flush on the transaction worker threads.
+ * Client will move to FLUSHING state only when a direct call to
+ * lwis_transaction_client_flush has been made.
+ * Once the flush is complete, the client will transition back
+ * to NOT_FLUSHING state.
+ */
+enum lwis_client_flush_state {
+ NOT_FLUSHING = 0,
+ FLUSHING
+};
+
/* Forward declaration for lwis_device. This is needed for the declaration for
lwis_device_subclass_operations data struct. */
struct lwis_device;
@@ -294,6 +307,8 @@ struct lwis_device {
/* Worker thread */
struct kthread_worker transaction_worker;
struct task_struct *transaction_worker_thread;
+ /* Limit on number of transactions to be processed at a time */
+ int transaction_process_limit;
};
/*
@@ -345,6 +360,12 @@ struct lwis_client {
struct list_head node;
/* Mark if the client called device enable */
bool is_enabled;
+ /* Work item to schedule I2C transfers */
+ struct kthread_work i2c_work;
+ /* Indicates if the client has been issued a flush worker call */
+ enum lwis_client_flush_state flush_state;
+ /* Lock to guard client's flush state changes */
+ spinlock_t flush_lock;
};
/*
diff --git a/lwis_device_i2c.c b/lwis_device_i2c.c
index ae74232..877719f 100644
--- a/lwis_device_i2c.c
+++ b/lwis_device_i2c.c
@@ -7,7 +7,6 @@
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
-
#define pr_fmt(fmt) KBUILD_MODNAME "-i2c-dev: " fmt
#include "lwis_device_i2c.h"
@@ -274,26 +273,14 @@ static int lwis_i2c_device_probe(struct platform_device *plat_dev)
goto error_probe;
}
- /* Create associated kworker threads */
- ret = lwis_create_kthread_workers(&i2c_dev->base_dev);
+ /* Create I2C Bus Manager */
+ ret = lwis_i2c_bus_manager_create(i2c_dev);
if (ret) {
- dev_err(i2c_dev->base_dev.dev, "Failed to create lwis_i2c_kthread");
+ dev_err(i2c_dev->base_dev.dev, "Error in i2c bus manager creation\n");
lwis_base_unprobe(&i2c_dev->base_dev);
goto error_probe;
}
- if (i2c_dev->base_dev.transaction_thread_priority != 0) {
- ret = lwis_set_kthread_priority(&i2c_dev->base_dev,
- i2c_dev->base_dev.transaction_worker_thread,
- i2c_dev->base_dev.transaction_thread_priority);
- if (ret) {
- dev_err(i2c_dev->base_dev.dev,
- "Failed to set LWIS I2C transaction kthread priority (%d)", ret);
- lwis_base_unprobe(&i2c_dev->base_dev);
- goto error_probe;
- }
- }
-
dev_info(i2c_dev->base_dev.dev, "I2C Device Probe: Success\n");
return 0;
@@ -375,6 +362,8 @@ int __init lwis_i2c_device_init(void)
pr_info("I2C device initialization\n");
+ lwis_i2c_bus_manager_list_initialize();
+
ret = platform_driver_register(&lwis_driver);
if (ret) {
pr_err("platform_driver_register failed: %d\n", ret);
@@ -389,6 +378,8 @@ int __init lwis_i2c_device_init(void)
int lwis_i2c_device_deinit(void)
{
+ lwis_i2c_bus_manager_list_deinitialize();
+
platform_driver_unregister(&lwis_driver);
return 0;
}
diff --git a/lwis_device_i2c.h b/lwis_device_i2c.h
index 7d52d1f..393bd32 100644
--- a/lwis_device_i2c.h
+++ b/lwis_device_i2c.h
@@ -15,6 +15,7 @@
#include <linux/pinctrl/consumer.h>
#include "lwis_device.h"
+#include "lwis_i2c_bus_manager.h"
#define MAX_I2C_LOCK_NUM 8
@@ -33,6 +34,9 @@ struct lwis_i2c_device {
u32 i2c_lock_group_id;
/* Mutex shared by the same group id's I2C devices */
struct mutex *group_i2c_lock;
+ /* Pointer to the I2C bus manager for this device */
+ struct lwis_i2c_bus_manager *i2c_bus_manager;
+ int device_priority;
};
int lwis_i2c_device_deinit(void);
diff --git a/lwis_device_top.c b/lwis_device_top.c
index 3a3b0c3..3cd17a7 100644
--- a/lwis_device_top.c
+++ b/lwis_device_top.c
@@ -115,7 +115,7 @@ static struct lwis_event_subscriber_list *event_subscriber_list_create(struct lw
struct lwis_top_device *lwis_top_dev =
container_of(lwis_dev, struct lwis_top_device, base_dev);
struct lwis_event_subscriber_list *event_subscriber_list =
- kmalloc(sizeof(struct lwis_event_subscriber_list), GFP_KERNEL);
+ kmalloc(sizeof(struct lwis_event_subscriber_list), GFP_ATOMIC);
if (!event_subscriber_list) {
dev_err(lwis_dev->dev, "Can't allocate event subscriber list\n");
return NULL;
@@ -601,4 +601,4 @@ int lwis_top_device_deinit(void)
{
platform_driver_unregister(&lwis_driver);
return 0;
-} \ No newline at end of file
+}
diff --git a/lwis_dt.c b/lwis_dt.c
index d6c4559..c844ce9 100644
--- a/lwis_dt.c
+++ b/lwis_dt.c
@@ -25,6 +25,7 @@
#include "lwis_i2c.h"
#include "lwis_ioreg.h"
#include "lwis_regulator.h"
+#include "lwis_i2c_bus_manager.h"
#define SHARED_STRING "shared-"
#define PULSE_STRING "pulse-"
@@ -1165,6 +1166,33 @@ static int parse_thread_priority(struct lwis_device *lwis_dev)
return 0;
}
+static int parse_i2c_device_priority(struct lwis_i2c_device *i2c_dev)
+{
+ struct device_node *dev_node;
+ int ret = 0;
+
+ dev_node = i2c_dev->base_dev.plat_dev->dev.of_node;
+ /* Set i2c device_priority value to default */
+ i2c_dev->device_priority = I2C_DEVICE_HIGH_PRIORITY;
+
+ ret = of_property_read_u32(dev_node, "i2c-device-priority", &i2c_dev->device_priority);
+ /* If no property in device tree, just return to use default */
+ if (ret == -EINVAL) {
+ return 0;
+ }
+ if (ret) {
+ pr_err("invalid i2c-device-priority value\n");
+ return ret;
+ }
+ if ((i2c_dev->device_priority < I2C_DEVICE_HIGH_PRIORITY) ||
+ (i2c_dev->device_priority > I2C_DEVICE_LOW_PRIORITY)) {
+ pr_err("invalid i2c-device-priority value %d\n", i2c_dev->device_priority);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static int parse_i2c_lock_group_id(struct lwis_i2c_device *i2c_dev)
{
struct device_node *dev_node;
@@ -1191,6 +1219,19 @@ static int parse_i2c_lock_group_id(struct lwis_i2c_device *i2c_dev)
return 0;
}
+static int parse_transaction_process_limit(struct lwis_device *lwis_dev)
+{
+ struct device_node *dev_node;
+
+ lwis_dev->transaction_process_limit = 0;
+ dev_node = lwis_dev->plat_dev->dev.of_node;
+
+ of_property_read_u32(dev_node, "transaction-process-limit",
+ &lwis_dev->transaction_process_limit);
+
+ return 0;
+}
+
int lwis_base_parse_dt(struct lwis_device *lwis_dev)
{
struct device *dev;
@@ -1321,6 +1362,7 @@ int lwis_base_parse_dt(struct lwis_device *lwis_dev)
parse_access_mode(lwis_dev);
parse_thread_priority(lwis_dev);
parse_bitwidths(lwis_dev);
+ parse_transaction_process_limit(lwis_dev);
lwis_dev->bts_scenario_name = NULL;
of_property_read_string(dev_node, "bts-scenario", &lwis_dev->bts_scenario_name);
@@ -1364,6 +1406,12 @@ int lwis_i2c_device_parse_dt(struct lwis_i2c_device *i2c_dev)
return ret;
}
+ ret = parse_i2c_device_priority(i2c_dev);
+ if (ret) {
+ dev_err(i2c_dev->base_dev.dev, "Error parsing i2c device priority\n");
+ return ret;
+ }
+
return 0;
}
diff --git a/lwis_i2c.c b/lwis_i2c.c
index d375f3c..4ba20f4 100644
--- a/lwis_i2c.c
+++ b/lwis_i2c.c
@@ -73,10 +73,13 @@ static int perform_read_transfer(struct i2c_client *client, struct i2c_msg *msg,
const int num_msg = 2;
+ char trace_name[LWIS_MAX_NAME_STRING_LEN];
+ scnprintf(trace_name, LWIS_MAX_NAME_STRING_LEN, "i2c_read_%s", lwis_dev->name);
+
value_to_buf(offset, wbuf, offset_size_bytes);
- LWIS_ATRACE_FUNC_BEGIN(lwis_dev, "i2c_read");
+ LWIS_ATRACE_FUNC_BEGIN(lwis_dev, trace_name);
ret = i2c_transfer(client->adapter, msg, num_msg);
- LWIS_ATRACE_FUNC_END(lwis_dev, "i2c_read");
+ LWIS_ATRACE_FUNC_END(lwis_dev, trace_name);
return (ret == num_msg) ? 0 : ret;
}
@@ -89,11 +92,14 @@ static int perform_write_transfer(struct i2c_client *client, struct i2c_msg *msg
const int num_msg = 1;
+ char trace_name[LWIS_MAX_NAME_STRING_LEN];
+ scnprintf(trace_name, LWIS_MAX_NAME_STRING_LEN, "i2c_write_%s", lwis_dev->name);
+
value_to_buf(offset, buf, offset_size_bytes);
value_to_buf(value, buf + offset_size_bytes, value_size_bytes);
- LWIS_ATRACE_FUNC_BEGIN(lwis_dev, "i2c_write");
+ LWIS_ATRACE_FUNC_BEGIN(lwis_dev, trace_name);
ret = i2c_transfer(client->adapter, msg, num_msg);
- LWIS_ATRACE_FUNC_END(lwis_dev, "i2c_write");
+ LWIS_ATRACE_FUNC_END(lwis_dev, trace_name);
return (ret == num_msg) ? 0 : ret;
}
@@ -107,12 +113,15 @@ static int perform_write_batch_transfer(struct i2c_client *client, struct i2c_ms
const int num_msg = 1;
+ char trace_name[LWIS_MAX_NAME_STRING_LEN];
+ scnprintf(trace_name, LWIS_MAX_NAME_STRING_LEN, "i2c_write_batch_%s", lwis_dev->name);
+
value_to_buf(offset, buf, offset_size_bytes);
memcpy(buf + offset_size_bytes, value_buf, value_size_bytes);
- LWIS_ATRACE_FUNC_BEGIN(lwis_dev, "i2c_write_batch");
+ LWIS_ATRACE_FUNC_BEGIN(lwis_dev, trace_name);
ret = i2c_transfer(client->adapter, msg, num_msg);
- LWIS_ATRACE_FUNC_END(lwis_dev, "i2c_write_batch");
+ LWIS_ATRACE_FUNC_END(lwis_dev, trace_name);
return (ret == num_msg) ? 0 : ret;
}
diff --git a/lwis_i2c_bus_manager.c b/lwis_i2c_bus_manager.c
new file mode 100644
index 0000000..c030f27
--- /dev/null
+++ b/lwis_i2c_bus_manager.c
@@ -0,0 +1,751 @@
+/*
+ * Google LWIS I2C BUS Manager
+ *
+ * Copyright (c) 2023 Google, LLC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME "-i2c-bus-manager: " fmt
+
+#include "lwis_device.h"
+#include "lwis_i2c_bus_manager.h"
+#include "lwis_device_i2c.h"
+#include "lwis_i2c_sched.h"
+
+bool lwis_i2c_bus_manager_debug;
+module_param(lwis_i2c_bus_manager_debug, bool, 0644);
+
+/* Defines the global list of bus managers shared among various I2C devices
+ * Each manager would control the transfers on a single I2C bus */
+static struct mutex i2c_bus_manager_list_lock;
+static struct lwis_i2c_bus_manager_list i2c_bus_manager_list;
+
+/*
+ * insert_bus_manager_id_in_list:
+ * Inserts the newly created instance of I2C bus manager in the list
+*/
+static int insert_bus_manager_id_in_list(struct lwis_i2c_bus_manager *i2c_bus_manager,
+ int i2c_bus_handle)
+{
+ struct lwis_i2c_bus_manager_identifier *i2c_bus_manager_identifier_node = NULL;
+
+ if (!i2c_bus_manager)
+ return -EINVAL;
+
+ i2c_bus_manager_identifier_node =
+ kzalloc(sizeof(struct lwis_i2c_bus_manager_identifier), GFP_KERNEL);
+ if (!i2c_bus_manager_identifier_node) {
+ pr_err("Failed to allocate lwis i2c bus manager id list node\n");
+ return -ENOMEM;
+ }
+
+ i2c_bus_manager_identifier_node->i2c_bus_manager_handle = i2c_bus_handle;
+ i2c_bus_manager_identifier_node->i2c_bus_manager = i2c_bus_manager;
+ INIT_LIST_HEAD(&i2c_bus_manager_identifier_node->i2c_bus_manager_list_node);
+
+ mutex_lock(&i2c_bus_manager_list_lock);
+ list_add_tail(&i2c_bus_manager_identifier_node->i2c_bus_manager_list_node,
+ &i2c_bus_manager_list.i2c_bus_manager_list_head);
+ mutex_unlock(&i2c_bus_manager_list_lock);
+
+ return 0;
+}
+
+/*
+ * delete_bus_manager_id_in_list:
+ * Deletes the newly created instance of I2C bus manager in the list
+*/
+static void delete_bus_manager_id_in_list(int i2c_bus_handle)
+{
+ struct lwis_i2c_bus_manager_identifier *i2c_bus_manager_identifier_node = NULL;
+ struct list_head *i2c_bus_manager_list_node = NULL;
+ struct list_head *i2c_bus_manager_list_tmp_node = NULL;
+
+ mutex_lock(&i2c_bus_manager_list_lock);
+ list_for_each_safe (i2c_bus_manager_list_node, i2c_bus_manager_list_tmp_node,
+ &i2c_bus_manager_list.i2c_bus_manager_list_head) {
+ i2c_bus_manager_identifier_node = list_entry(i2c_bus_manager_list_node,
+ struct lwis_i2c_bus_manager_identifier,
+ i2c_bus_manager_list_node);
+ if (i2c_bus_manager_identifier_node->i2c_bus_manager_handle == i2c_bus_handle) {
+ list_del(&i2c_bus_manager_identifier_node->i2c_bus_manager_list_node);
+ kfree(i2c_bus_manager_identifier_node);
+ i2c_bus_manager_identifier_node = NULL;
+ break;
+ }
+ }
+ mutex_unlock(&i2c_bus_manager_list_lock);
+}
+
+/*
+ * find_i2c_bus_manager:
+ * Returns a valid I2C Bus Manager for a valid i2c_bus_handle.
+ * Returns NULL if the bus manager hasn't been created for this handle.
+*/
+static struct lwis_i2c_bus_manager *find_i2c_bus_manager(int i2c_bus_handle)
+{
+ struct lwis_i2c_bus_manager *i2c_bus_manager = NULL;
+ struct list_head *i2c_bus_manager_list_node = NULL;
+ struct list_head *i2c_bus_manager_list_tmp_node = NULL;
+ struct lwis_i2c_bus_manager_identifier *i2c_bus_manager_identifier = NULL;
+
+ mutex_lock(&i2c_bus_manager_list_lock);
+ list_for_each_safe (i2c_bus_manager_list_node, i2c_bus_manager_list_tmp_node,
+ &i2c_bus_manager_list.i2c_bus_manager_list_head) {
+ i2c_bus_manager_identifier = list_entry(i2c_bus_manager_list_node,
+ struct lwis_i2c_bus_manager_identifier,
+ i2c_bus_manager_list_node);
+ if (i2c_bus_manager_identifier->i2c_bus_manager_handle == i2c_bus_handle) {
+ i2c_bus_manager = i2c_bus_manager_identifier->i2c_bus_manager;
+ break;
+ }
+ }
+ mutex_unlock(&i2c_bus_manager_list_lock);
+
+ return i2c_bus_manager;
+}
+
+/*
+ * create_i2c_kthread_workers:
+ * Creates I2C worker threads, one per bus
+*/
+static int create_i2c_kthread_workers(struct lwis_i2c_bus_manager *i2c_bus_manager,
+ struct lwis_device *lwis_dev)
+{
+ char i2c_bus_thread_name[LWIS_MAX_NAME_STRING_LEN];
+ if (!i2c_bus_manager) {
+ dev_err(lwis_dev->dev, "lwis_create_kthread_workers: I2C Bus Manager is NULL\n");
+ return -ENODEV;
+ }
+ scnprintf(i2c_bus_thread_name, LWIS_MAX_NAME_STRING_LEN, "lwis_%s",
+ i2c_bus_manager->i2c_bus_name);
+ kthread_init_worker(&i2c_bus_manager->i2c_bus_worker);
+ i2c_bus_manager->i2c_bus_worker_thread = kthread_run(
+ kthread_worker_fn, &i2c_bus_manager->i2c_bus_worker, i2c_bus_thread_name);
+ if (IS_ERR(i2c_bus_manager->i2c_bus_worker_thread)) {
+ dev_err(lwis_dev->dev, "Creation of i2c_bus_worker_thread failed for bus %s\n",
+ i2c_bus_manager->i2c_bus_name);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*
+ * check_i2c_thread_priority:
+ * Checks if the lwis device being connected has the same priority as other I2C threads
+ * Prints a warning message if there is a difference between the priorities
+*/
+static void check_i2c_thread_priority(struct lwis_i2c_bus_manager *i2c_bus_manager,
+ struct lwis_device *lwis_dev)
+{
+ if (i2c_bus_manager->i2c_bus_thread_priority != lwis_dev->transaction_thread_priority) {
+ dev_warn(
+ lwis_dev->dev,
+ "I2C bus manager thread %s priority(%d) is not the same as device thread priority(%d)\n",
+ i2c_bus_manager->i2c_bus_name, i2c_bus_manager->i2c_bus_thread_priority,
+ lwis_dev->transaction_thread_priority);
+ }
+}
+
+/*
+ * set_i2c_thread_priority:
+ * Sets the priority for I2C threads
+*/
+static int set_i2c_thread_priority(struct lwis_i2c_bus_manager *i2c_bus_manager,
+ struct lwis_device *lwis_dev)
+{
+ int ret = 0;
+ i2c_bus_manager->i2c_bus_thread_priority = lwis_dev->transaction_thread_priority;
+ if (i2c_bus_manager->i2c_bus_thread_priority != 0) {
+ ret = lwis_set_kthread_priority(lwis_dev, i2c_bus_manager->i2c_bus_worker_thread,
+ i2c_bus_manager->i2c_bus_thread_priority);
+ }
+ return ret;
+}
+
+/*
+ * is_valid_connected_device:
+ * Makes sure a valid client connected to this I2C executes the job on this manager
+ */
+static bool is_valid_connected_device(struct lwis_device *lwis_dev,
+ struct lwis_i2c_bus_manager *i2c_bus_manager)
+{
+ struct lwis_i2c_connected_device *connected_i2c_device;
+ struct list_head *i2c_connected_device_node, *i2c_connected_device_tmp_node;
+
+ if ((lwis_dev == NULL) || (i2c_bus_manager == NULL)) {
+ return false;
+ }
+
+ list_for_each_safe (i2c_connected_device_node, i2c_connected_device_tmp_node,
+ &i2c_bus_manager->i2c_connected_devices) {
+ connected_i2c_device =
+ list_entry(i2c_connected_device_node, struct lwis_i2c_connected_device,
+ connected_device_node);
+ if (connected_i2c_device->connected_device == lwis_dev) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * set_i2c_bus_manager_name:
+ * Builds and sets the I2C Bus manager name
+*/
+static void set_i2c_bus_manager_name(struct lwis_i2c_bus_manager *i2c_bus_manager)
+{
+ scnprintf(i2c_bus_manager->i2c_bus_name, LWIS_MAX_NAME_STRING_LEN, "I2C_Bus_%d",
+ i2c_bus_manager->i2c_bus_id);
+}
+
+/*
+ * destroy_i2c_bus_manager:
+ * Destroys this instance of the I2C bus manager
+ */
+static void destroy_i2c_bus_manager(struct lwis_i2c_bus_manager *i2c_bus_manager,
+ struct lwis_device *lwis_dev)
+{
+ int i = 0;
+ if (!i2c_bus_manager) {
+ return;
+ }
+
+ dev_info(lwis_dev->dev, "Destroying I2C Bus Manager: %s\n", i2c_bus_manager->i2c_bus_name);
+ mutex_lock(&i2c_bus_manager->i2c_process_queue_lock);
+ for (i = 0; i < I2C_MAX_PRIORITY_LEVELS; i++) {
+ lwis_i2c_process_request_queue_destroy(&i2c_bus_manager->i2c_bus_process_queue[i]);
+ }
+ mutex_unlock(&i2c_bus_manager->i2c_process_queue_lock);
+
+ /* Delete the bus manager instance from the list */
+ delete_bus_manager_id_in_list(i2c_bus_manager->i2c_bus_id);
+
+ /* Free the bus manager */
+ kfree(i2c_bus_manager);
+ i2c_bus_manager = NULL;
+}
+
+/*
+ * connect_i2c_bus_manager:
+ * Connects a lwis device to this instance of the I2C bus manager.
+*/
+static int connect_i2c_bus_manager(struct lwis_i2c_bus_manager *i2c_bus_manager,
+ struct lwis_device *lwis_dev)
+{
+ int ret = 0;
+ struct lwis_i2c_connected_device *connected_i2c_device;
+
+ if ((!lwis_dev) || (!i2c_bus_manager)) {
+ pr_err("Null lwis device or bus manager\n");
+ return -EINVAL;
+ }
+
+ if (!lwis_check_device_type(lwis_dev, DEVICE_TYPE_I2C)) {
+ dev_err(lwis_dev->dev,
+ "Failed trying to connect non I2C device to a I2C bus manager\n");
+ return -EINVAL;
+ }
+
+ connected_i2c_device = kzalloc(sizeof(struct lwis_i2c_connected_device), GFP_KERNEL);
+ if (!connected_i2c_device) {
+ dev_err(lwis_dev->dev, "Failed to connect device to I2C Bus Manager\n");
+ return -ENOMEM;
+ }
+ connected_i2c_device->connected_device = lwis_dev;
+ INIT_LIST_HEAD(&connected_i2c_device->connected_device_node);
+ list_add_tail(&connected_i2c_device->connected_device_node,
+ &i2c_bus_manager->i2c_connected_devices);
+ i2c_bus_manager->number_of_connected_devices++;
+
+ return ret;
+}
+
+static bool i2c_device_priority_is_valid(int device_priority)
+{
+ if ((device_priority >= I2C_DEVICE_HIGH_PRIORITY) &&
+ (device_priority <= I2C_DEVICE_LOW_PRIORITY)) {
+ return true;
+ }
+ return false;
+}
+
+/*
+ * lwis_i2c_bus_manager_process_worker_queue:
+ * Function to be called by i2c bus manager worker thread to
+ * pick the next I2C client that is scheduled for transfer.
+ * The process queue will be processed in order of I2C
+ * device priority.
+ */
+void lwis_i2c_bus_manager_process_worker_queue(struct lwis_client *client)
+{
+ /* Get the correct I2C Bus manager to process it's queue */
+ struct lwis_device *lwis_dev = NULL;
+ struct lwis_i2c_bus_manager *i2c_bus_manager = NULL;
+ int i = 0;
+
+ /* The transfers will be processed in fifo order */
+ struct lwis_client *client_to_process = NULL;
+ struct lwis_device *lwis_dev_to_process = NULL;
+ struct lwis_i2c_process_queue *process_queue = NULL;
+ struct lwis_i2c_process_request *process_request = NULL;
+
+ struct list_head *i2c_client_node, *i2c_client_tmp_node;
+
+ lwis_dev = client->lwis_dev;
+ i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
+
+ if (lwis_i2c_bus_manager_debug) {
+ dev_info(lwis_dev->dev, "%s scheduled by %s\n", i2c_bus_manager->i2c_bus_name,
+ lwis_dev->name);
+ }
+
+ if (!i2c_bus_manager) {
+ dev_err(lwis_dev->dev, "I2C Bus Manager is null\n");
+ return;
+ }
+
+ mutex_lock(&i2c_bus_manager->i2c_process_queue_lock);
+ for (i = 0; i < I2C_MAX_PRIORITY_LEVELS; i++) {
+ process_queue = &i2c_bus_manager->i2c_bus_process_queue[i];
+ list_for_each_safe (i2c_client_node, i2c_client_tmp_node, &process_queue->head) {
+ if (lwis_i2c_bus_manager_debug) {
+ dev_info(lwis_dev->dev,
+ "Process request nodes for %s: cur %p tmp %p\n",
+ i2c_bus_manager->i2c_bus_name, i2c_client_node,
+ i2c_client_tmp_node);
+ }
+ process_request = list_entry(i2c_client_node,
+ struct lwis_i2c_process_request, request_node);
+ if (!process_request) {
+ dev_err(lwis_dev->dev, "I2C Bus Worker process_request is null\n");
+ break;
+ }
+
+ client_to_process = process_request->requesting_client;
+ if (!client_to_process) {
+ dev_err(lwis_dev->dev,
+ "I2C Bus Worker client_to_process is null\n");
+ break;
+ }
+
+ lwis_dev_to_process = client_to_process->lwis_dev;
+ if (!lwis_dev_to_process) {
+ dev_err(lwis_dev->dev,
+ "I2C Bus Worker lwis_dev_to_process is null\n");
+ break;
+ }
+
+ if (lwis_i2c_bus_manager_debug) {
+ dev_info(lwis_dev_to_process->dev, "Processing client start %s\n",
+ lwis_dev_to_process->name);
+ }
+
+ if (is_valid_connected_device(lwis_dev_to_process, i2c_bus_manager)) {
+ lwis_process_transactions_in_queue(client_to_process);
+ lwis_process_periodic_io_in_queue(client_to_process);
+ }
+
+ if (lwis_i2c_bus_manager_debug) {
+ dev_info(lwis_dev_to_process->dev, "Processing client end %s\n",
+ lwis_dev_to_process->name);
+ }
+ }
+ }
+ mutex_unlock(&i2c_bus_manager->i2c_process_queue_lock);
+}
+
+/*
+ * lwis_i2c_bus_manager_create:
+ * Creates a new instance of I2C bus manager
+ */
+int lwis_i2c_bus_manager_create(struct lwis_i2c_device *i2c_dev)
+{
+ int ret = 0;
+ int i = 0;
+ struct lwis_i2c_bus_manager *i2c_bus_manager = NULL;
+ struct lwis_device *i2c_base_device = &i2c_dev->base_dev;
+
+ if (!lwis_check_device_type(i2c_base_device, DEVICE_TYPE_I2C)) {
+ return 0;
+ }
+
+ i2c_bus_manager = find_i2c_bus_manager(i2c_dev->adapter->nr);
+ if (!i2c_bus_manager) {
+ /* Allocate memory for I2C Bus Manager */
+ i2c_bus_manager = kzalloc(sizeof(struct lwis_i2c_bus_manager), GFP_KERNEL);
+ if (!i2c_bus_manager) {
+ dev_err(i2c_base_device->dev, "Failed to allocate lwis i2c bus manager\n");
+ return -ENOMEM;
+ }
+
+ i2c_bus_manager->i2c_bus_id = i2c_dev->adapter->nr;
+ set_i2c_bus_manager_name(i2c_bus_manager);
+
+ /* Mutex and Lock initializations */
+ mutex_init(&i2c_bus_manager->i2c_bus_lock);
+ mutex_init(&i2c_bus_manager->i2c_process_queue_lock);
+
+ /* List initializations */
+ INIT_LIST_HEAD(&i2c_bus_manager->i2c_connected_devices);
+
+ /* Create a I2C transfer process queue */
+ for (i = 0; i < I2C_MAX_PRIORITY_LEVELS; i++) {
+ lwis_i2c_process_request_queue_initialize(
+ &i2c_bus_manager->i2c_bus_process_queue[i]);
+ }
+
+ /* Insert this instance of bus manager in the bus manager list */
+ ret = insert_bus_manager_id_in_list(i2c_bus_manager, i2c_dev->adapter->nr);
+ if (ret < 0) {
+ goto error_creating_i2c_bus_manager;
+ }
+
+ /* Create worker thread to serve this bus manager */
+ ret = create_i2c_kthread_workers(i2c_bus_manager, i2c_base_device);
+ if (ret < 0) {
+ goto error_creating_i2c_bus_manager;
+ }
+
+ /* Set priority for the worker threads */
+ ret = set_i2c_thread_priority(i2c_bus_manager, i2c_base_device);
+ if (ret < 0) {
+ goto error_creating_i2c_bus_manager;
+ }
+ }
+
+ /* Check the current device's thread priority with respect to the bus priority */
+ check_i2c_thread_priority(i2c_bus_manager, i2c_base_device);
+
+ /* Connect this lwis device to the I2C Bus manager found/created */
+ ret = connect_i2c_bus_manager(i2c_bus_manager, i2c_base_device);
+ if (ret < 0) {
+ goto error_creating_i2c_bus_manager;
+ }
+
+ dev_info(i2c_base_device->dev,
+ "I2C Bus Manager: %s Connected Device: %s Connected device count: %d\n",
+ i2c_bus_manager->i2c_bus_name, i2c_base_device->name,
+ i2c_bus_manager->number_of_connected_devices);
+
+ i2c_dev->i2c_bus_manager = i2c_bus_manager;
+ return ret;
+
+error_creating_i2c_bus_manager:
+ dev_err(i2c_base_device->dev, "Error creating I2C Bus Manager\n");
+ if (i2c_bus_manager) {
+ kfree(i2c_bus_manager);
+ i2c_bus_manager = NULL;
+ }
+ return -EINVAL;
+}
+
+/*
+ * lwis_i2c_bus_manager_disconnect:
+ * Disconnects a lwis device from this instance of the I2C bus manager.
+ * Doesn't destroy the instance of I2C bus manager
+*/
+void lwis_i2c_bus_manager_disconnect(struct lwis_device *lwis_dev)
+{
+ struct lwis_i2c_bus_manager *i2c_bus_manager;
+ struct lwis_i2c_connected_device *connected_i2c_device;
+ struct list_head *i2c_connected_device_node, *i2c_connected_device_tmp_node;
+ struct lwis_i2c_device *i2c_dev = NULL;
+
+ i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
+ if (!i2c_bus_manager) {
+ return;
+ }
+
+ list_for_each_safe (i2c_connected_device_node, i2c_connected_device_tmp_node,
+ &i2c_bus_manager->i2c_connected_devices) {
+ connected_i2c_device =
+ list_entry(i2c_connected_device_node, struct lwis_i2c_connected_device,
+ connected_device_node);
+ /* Reset the bus manager pointer for this i2c device */
+ i2c_dev = container_of(lwis_dev, struct lwis_i2c_device, base_dev);
+ i2c_dev->i2c_bus_manager = NULL;
+
+ if (connected_i2c_device->connected_device == lwis_dev) {
+ list_del(&connected_i2c_device->connected_device_node);
+ kfree(connected_i2c_device);
+ connected_i2c_device = NULL;
+ --i2c_bus_manager->number_of_connected_devices;
+
+ /* Destroy the bus manager instance if there
+ * are no more I2C devices connected to it
+ */
+ if (i2c_bus_manager->number_of_connected_devices == 0) {
+ destroy_i2c_bus_manager(i2c_bus_manager, lwis_dev);
+ }
+ return;
+ }
+ }
+}
+
+/* lwis_i2c_bus_manager_lock_i2c_bus:
+ * Locks the I2C bus for a given I2C Lwis Device
+ */
+void lwis_i2c_bus_manager_lock_i2c_bus(struct lwis_device *lwis_dev)
+{
+ struct lwis_i2c_bus_manager *i2c_bus_manager = NULL;
+ i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
+ if (i2c_bus_manager) {
+ mutex_lock(&i2c_bus_manager->i2c_bus_lock);
+ if (lwis_i2c_bus_manager_debug) {
+ dev_info(lwis_dev->dev, "%s lock\n", i2c_bus_manager->i2c_bus_name);
+ }
+ }
+}
+
+/* lwis_i2c_bus_manager_unlock_i2c_bus:
+ * Unlocks the I2C bus for a given I2C Lwis Device
+ */
+void lwis_i2c_bus_manager_unlock_i2c_bus(struct lwis_device *lwis_dev)
+{
+ struct lwis_i2c_bus_manager *i2c_bus_manager = NULL;
+ i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
+ if (i2c_bus_manager) {
+ if (lwis_i2c_bus_manager_debug) {
+ dev_info(lwis_dev->dev, "%s unlock\n", i2c_bus_manager->i2c_bus_name);
+ }
+ mutex_unlock(&i2c_bus_manager->i2c_bus_lock);
+ }
+}
+
+/* lwis_i2c_bus_managlwis_i2c_bus_manager_get_managerr_get:
+ * Gets I2C Bus Manager for a given lwis device
+ */
+struct lwis_i2c_bus_manager *lwis_i2c_bus_manager_get_manager(struct lwis_device *lwis_dev)
+{
+ struct lwis_i2c_device *i2c_dev = NULL;
+ if (lwis_check_device_type(lwis_dev, DEVICE_TYPE_I2C)) {
+ i2c_dev = container_of(lwis_dev, struct lwis_i2c_device, base_dev);
+ if (i2c_dev) {
+ return i2c_dev->i2c_bus_manager;
+ }
+ }
+ return NULL;
+}
+
+/* lwis_i2c_bus_manager_flush_i2c_worker:
+ * Flushes the I2C Bus Manager worker
+ */
+void lwis_i2c_bus_manager_flush_i2c_worker(struct lwis_device *lwis_dev)
+{
+ struct lwis_i2c_bus_manager *i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
+
+ if (i2c_bus_manager == NULL)
+ return;
+
+ kthread_flush_worker(&i2c_bus_manager->i2c_bus_worker);
+}
+
+/* lwis_i2c_bus_manager_list_initialize:
+ * Initializes bus manager global list. This is the list that holds
+ * actual bus manager pointers for a given physical I2C Bus connection
+ */
+void lwis_i2c_bus_manager_list_initialize(void)
+{
+ /* initialize_i2c_bus_manager_list */
+ mutex_init(&i2c_bus_manager_list_lock);
+ INIT_LIST_HEAD(&i2c_bus_manager_list.i2c_bus_manager_list_head);
+}
+
+/* lwis_i2c_bus_manager_list_deinitialize:
+ * Deinitializes bus manager global list
+ */
+void lwis_i2c_bus_manager_list_deinitialize(void)
+{
+ struct list_head *i2c_bus_manager_list_node, *i2c_bus_manager_list_tmp_node;
+ struct lwis_i2c_bus_manager_identifier *i2c_bus_manager_identifier;
+
+ /* deinitialize_i2c_bus_manager_list */
+ mutex_lock(&i2c_bus_manager_list_lock);
+ list_for_each_safe (i2c_bus_manager_list_node, i2c_bus_manager_list_tmp_node,
+ &i2c_bus_manager_list.i2c_bus_manager_list_head) {
+ i2c_bus_manager_identifier = list_entry(i2c_bus_manager_list_node,
+ struct lwis_i2c_bus_manager_identifier,
+ i2c_bus_manager_list_node);
+ i2c_bus_manager_identifier->i2c_bus_manager = NULL;
+ list_del(&i2c_bus_manager_identifier->i2c_bus_manager_list_node);
+ kfree(i2c_bus_manager_identifier);
+ i2c_bus_manager_identifier = NULL;
+ }
+ mutex_unlock(&i2c_bus_manager_list_lock);
+}
+
+/* lwis_i2c_bus_manager_connect_client:
+ * Connects a lwis client to the bus manager to be processed by the worker.
+ * The client will be connected to the appropriate priority queue based
+ * on the I2C device priority specified in the dts for the I2C device node.
+ * I2C lwis client is always connected when a new instance of client is
+ * created.
+ */
+int lwis_i2c_bus_manager_connect_client(struct lwis_client *connecting_client)
+{
+ int ret = 0;
+ int device_priority = I2C_MAX_PRIORITY_LEVELS;
+ bool create_client_node = true;
+ struct lwis_i2c_process_request *i2c_connecting_client_node;
+ struct lwis_device *lwis_dev = NULL;
+ struct lwis_i2c_process_queue *process_queue = NULL;
+ struct lwis_i2c_device *i2c_dev = NULL;
+ struct lwis_i2c_bus_manager *i2c_bus_manager = NULL;
+ struct list_head *request, *request_tmp;
+ struct lwis_i2c_process_request *search_node;
+
+ if (!connecting_client) {
+ pr_err("Connecting client pointer for I2C Bus Manager is NULL\n");
+ return -EINVAL;
+ }
+
+ lwis_dev = connecting_client->lwis_dev;
+ if (!lwis_dev) {
+ pr_err("Connecting device for I2C Bus Manager is NULL\n");
+ return -EINVAL;
+ }
+
+ if (!lwis_check_device_type(lwis_dev, DEVICE_TYPE_I2C)) {
+ return ret;
+ }
+
+ i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
+ if (!i2c_bus_manager) {
+ dev_err(lwis_dev->dev, "I2C bus manager is NULL\n");
+ return -EINVAL;
+ }
+
+ i2c_dev = container_of(lwis_dev, struct lwis_i2c_device, base_dev);
+ if (!i2c_dev) {
+ dev_err(lwis_dev->dev, "I2C device is NULL\n");
+ return -EINVAL;
+ }
+
+ device_priority = i2c_dev->device_priority;
+ if (!i2c_device_priority_is_valid(device_priority)) {
+ dev_err(lwis_dev->dev, "Invalid I2C device priority %d\n", device_priority);
+ return -EINVAL;
+ }
+
+ mutex_lock(&i2c_bus_manager->i2c_process_queue_lock);
+
+ // Search for existing client node in the queue, if client is already connected
+ // to this bus then don't create a new client node
+ process_queue = &i2c_bus_manager->i2c_bus_process_queue[device_priority];
+ if (!lwis_i2c_process_request_queue_is_empty(process_queue)) {
+ list_for_each_safe (request, request_tmp, &process_queue->head) {
+ search_node =
+ list_entry(request, struct lwis_i2c_process_request, request_node);
+ if (search_node->requesting_client == connecting_client) {
+ dev_info(lwis_dev->dev,
+ "I2C client already connected %s(%p) to bus %s \n",
+ lwis_dev->name, connecting_client,
+ i2c_bus_manager->i2c_bus_name);
+ create_client_node = false;
+ break;
+ }
+ }
+ }
+
+ if (create_client_node) {
+ i2c_connecting_client_node =
+ kzalloc(sizeof(struct lwis_i2c_process_request), GFP_KERNEL);
+ if (!i2c_connecting_client_node) {
+ dev_err(lwis_dev->dev, "Failed to connect client to I2C Bus Manager\n");
+ return -ENOMEM;
+ }
+ i2c_connecting_client_node->requesting_client = connecting_client;
+ INIT_LIST_HEAD(&i2c_connecting_client_node->request_node);
+ list_add_tail(&i2c_connecting_client_node->request_node, &process_queue->head);
+ ++process_queue->number_of_nodes;
+ dev_info(lwis_dev->dev, "Connecting client %s(%p) to bus %s\n", lwis_dev->name,
+ connecting_client, i2c_bus_manager->i2c_bus_name);
+ if (lwis_i2c_bus_manager_debug) {
+ dev_info(lwis_dev->dev, "Adding process request %p\n",
+ i2c_connecting_client_node);
+ }
+ }
+
+ mutex_unlock(&i2c_bus_manager->i2c_process_queue_lock);
+ return ret;
+}
+
+/* lwis_i2c_bus_manager_disconnect_client:
+ * Disconnects a lwis client to the bus manager. This will make sure that
+ * the released client is not processed further by the I2C worker.
+ * The client will be disconnected from the appropriate priority queue based
+ * on the I2C device priority specified in the dts for the I2C device node.
+ * I2C lwis client is always disconnected when the instance of client is
+ * released/destroyed.
+ */
+void lwis_i2c_bus_manager_disconnect_client(struct lwis_client *disconnecting_client)
+{
+ int device_priority = I2C_MAX_PRIORITY_LEVELS;
+ struct lwis_i2c_process_request *i2c_disconnecting_client_node;
+ struct lwis_device *lwis_dev = NULL;
+ struct lwis_i2c_process_queue *process_queue = NULL;
+ struct lwis_i2c_device *i2c_dev = NULL;
+ struct list_head *request, *request_tmp;
+ struct lwis_i2c_bus_manager *i2c_bus_manager = NULL;
+
+ if (!disconnecting_client) {
+ pr_err("Disconnecting client pointer for I2C Bus Manager is NULL\n");
+ return;
+ }
+
+ lwis_dev = disconnecting_client->lwis_dev;
+ if (!lwis_dev) {
+ pr_err("Disconnecting device for I2C Bus Manager is NULL\n");
+ return;
+ }
+
+ if (!lwis_check_device_type(lwis_dev, DEVICE_TYPE_I2C)) {
+ return;
+ }
+
+ i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
+ if (!i2c_bus_manager) {
+ dev_err(lwis_dev->dev, "I2C bus manager is NULL\n");
+ return;
+ }
+
+ i2c_dev = container_of(lwis_dev, struct lwis_i2c_device, base_dev);
+ if (!i2c_dev) {
+ dev_err(lwis_dev->dev, "I2C device is NULL\n");
+ return;
+ }
+
+ device_priority = i2c_dev->device_priority;
+ if (!i2c_device_priority_is_valid(device_priority)) {
+ dev_err(lwis_dev->dev, "Invalid I2C device priority %d\n", device_priority);
+ return;
+ }
+
+ mutex_lock(&i2c_bus_manager->i2c_process_queue_lock);
+ process_queue = &i2c_bus_manager->i2c_bus_process_queue[device_priority];
+ list_for_each_safe (request, request_tmp, &process_queue->head) {
+ i2c_disconnecting_client_node =
+ list_entry(request, struct lwis_i2c_process_request, request_node);
+ if (i2c_disconnecting_client_node->requesting_client == disconnecting_client) {
+ dev_info(lwis_dev->dev, "Disconnecting I2C client %s(%p) from bus %s\n",
+ lwis_dev->name, disconnecting_client,
+ i2c_bus_manager->i2c_bus_name);
+ list_del(&i2c_disconnecting_client_node->request_node);
+ i2c_disconnecting_client_node->requesting_client = NULL;
+ if (lwis_i2c_bus_manager_debug) {
+ dev_info(lwis_dev->dev, "Freeing process request %p\n",
+ i2c_disconnecting_client_node);
+ }
+ kfree(i2c_disconnecting_client_node);
+ i2c_disconnecting_client_node = NULL;
+ --process_queue->number_of_nodes;
+ break;
+ }
+ }
+ mutex_unlock(&i2c_bus_manager->i2c_process_queue_lock);
+} \ No newline at end of file
diff --git a/lwis_i2c_bus_manager.h b/lwis_i2c_bus_manager.h
new file mode 100644
index 0000000..b278124
--- /dev/null
+++ b/lwis_i2c_bus_manager.h
@@ -0,0 +1,118 @@
+
+/*
+ * Google LWIS I2C Bus Manager
+ *
+ * Copyright (c) 2023 Google, LLC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef LWIS_I2C_BUS_MANAGER_H_
+#define LWIS_I2C_BUS_MANAGER_H_
+
+#include "lwis_device.h"
+#include "lwis_util.h"
+#include "lwis_periodic_io.h"
+#include "lwis_transaction.h"
+
+/* enum lwis_i2c_device_priority_level:
+ * Defines the I2C device priority level
+ * in which the requests will be executed
+ */
+enum lwis_i2c_device_priority_level {
+ I2C_DEVICE_HIGH_PRIORITY = 0,
+ I2C_DEVICE_MEDIUM_PRIORITY = 1,
+ I2C_DEVICE_LOW_PRIORITY = 2,
+ I2C_MAX_PRIORITY_LEVELS = 3
+};
+
+// Forward declaration
+struct lwis_i2c_device;
+
+/* struct lwis_i2c_bus_manager_list:
+ * Holds I2C bus manager list */
+struct lwis_i2c_bus_manager_list {
+ struct list_head i2c_bus_manager_list_head;
+};
+
+/* struct lwis_i2c_bus_manager_identifier:
+ * Holds a pointer to the I2C bus manager */
+struct lwis_i2c_bus_manager_identifier {
+ struct list_head i2c_bus_manager_list_node;
+ struct lwis_i2c_bus_manager *i2c_bus_manager;
+ int i2c_bus_manager_handle;
+};
+
+/* lwis_i2c_process_queue:
+ * This maintains the process queue for a given I2C bus.
+ * This is a collection of process request nodes that identify
+ * the lwis device requests in order they were queued.
+ * The scheduler is set to operate requests in a
+ * first in-first out manner, starting and updating the head
+ * and working towards the tail end. */
+struct lwis_i2c_process_queue {
+ /* Head node for the process queue */
+ struct list_head head;
+ /* Total number of devices that are queued to be processed */
+ int number_of_nodes;
+};
+
+/*
+ * struct lwis_i2c_bus_manager
+ * This defines the main attributes for I2C Bus Manager.
+ */
+struct lwis_i2c_bus_manager {
+ /* Unique identifier for this I2C bus manager */
+ int i2c_bus_id;
+ /* Name of I2C Bus manager corresponds to the name of the I2C Bus*/
+ char i2c_bus_name[LWIS_MAX_NAME_STRING_LEN];
+ /* Lock to control access to bus transfers */
+ struct mutex i2c_bus_lock;
+ /* Lock to control access to the I2C process queue for this bus */
+ struct mutex i2c_process_queue_lock;
+ /* I2C Bus thread priority */
+ u32 i2c_bus_thread_priority;
+ /* Worker thread */
+ struct kthread_worker i2c_bus_worker;
+ struct task_struct *i2c_bus_worker_thread;
+ /* Queue of all I2C devices that have data to transfer in their process queues */
+ struct lwis_i2c_process_queue i2c_bus_process_queue[I2C_MAX_PRIORITY_LEVELS];
+ /* List of I2C devices using this bus */
+ struct list_head i2c_connected_devices;
+ /* Total number of physically connected devices to the bus
+ * This count is set while probe/unprobe sequence */
+ int number_of_connected_devices;
+};
+
+/* This maintains the structure to identify the connected devices to a given I2C bus.
+ * This will be used to guard the bus against processing any illegal device entries */
+struct lwis_i2c_connected_device {
+ struct lwis_device *connected_device;
+ struct list_head connected_device_node;
+};
+
+void lwis_i2c_bus_manager_lock_i2c_bus(struct lwis_device *lwis_dev);
+
+void lwis_i2c_bus_manager_unlock_i2c_bus(struct lwis_device *lwis_dev);
+
+struct lwis_i2c_bus_manager *lwis_i2c_bus_manager_get_manager(struct lwis_device *lwis_dev);
+
+int lwis_i2c_bus_manager_create(struct lwis_i2c_device *i2c_dev);
+
+void lwis_i2c_bus_manager_disconnect(struct lwis_device *lwis_dev);
+
+void lwis_i2c_bus_manager_process_worker_queue(struct lwis_client *client);
+
+void lwis_i2c_bus_manager_flush_i2c_worker(struct lwis_device *lwis_dev);
+
+void lwis_i2c_bus_manager_list_initialize(void);
+
+void lwis_i2c_bus_manager_list_deinitialize(void);
+
+int lwis_i2c_bus_manager_connect_client(struct lwis_client *connecting_client);
+
+void lwis_i2c_bus_manager_disconnect_client(struct lwis_client *disconnecting_client);
+
+#endif /* LWIS_I2C_BUS_MANAGER_H */
diff --git a/lwis_i2c_sched.c b/lwis_i2c_sched.c
new file mode 100644
index 0000000..1ca802f
--- /dev/null
+++ b/lwis_i2c_sched.c
@@ -0,0 +1,62 @@
+/*
+ * Google LWIS I2C Bus Manager
+ *
+ * Copyright (c) 2023 Google, LLC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME "-i2c-sched: " fmt
+
+#include "lwis_i2c_sched.h"
+#include "lwis_i2c_bus_manager.h"
+
+/*
+ * lwis_i2c_process_request_queue_is_empty:
+ * Checks if the I2C process request queue is empty
+*/
+bool lwis_i2c_process_request_queue_is_empty(struct lwis_i2c_process_queue *process_queue)
+{
+ if ((!process_queue) || ((process_queue) && (process_queue->number_of_nodes == 0))) {
+ return true;
+ }
+ return false;
+}
+
+/*
+ * lwis_i2c_process_request_queue_initialize:
+ * Initializes the I2C process request queue for a given I2C Bus
+*/
+void lwis_i2c_process_request_queue_initialize(struct lwis_i2c_process_queue *process_queue)
+{
+ process_queue->number_of_nodes = 0;
+ INIT_LIST_HEAD(&process_queue->head);
+}
+
+/*
+ * lwis_i2c_process_request_queue_destroy:
+ * Frees all the requests in the queue
+*/
+void lwis_i2c_process_request_queue_destroy(struct lwis_i2c_process_queue *process_queue)
+{
+ struct list_head *request;
+ struct list_head *request_tmp;
+ struct lwis_i2c_process_request *process_request;
+
+ if (!process_queue)
+ return;
+
+ if (lwis_i2c_process_request_queue_is_empty(process_queue))
+ return;
+
+ list_for_each_safe (request, request_tmp, &process_queue->head) {
+ process_request =
+ list_entry(request, struct lwis_i2c_process_request, request_node);
+ list_del(&process_request->request_node);
+ process_request->requesting_client = NULL;
+ kfree(process_request);
+ process_request = NULL;
+ --process_queue->number_of_nodes;
+ }
+} \ No newline at end of file
diff --git a/lwis_i2c_sched.h b/lwis_i2c_sched.h
new file mode 100644
index 0000000..22073af
--- /dev/null
+++ b/lwis_i2c_sched.h
@@ -0,0 +1,33 @@
+/*
+ * Google LWIS I2C Bus Manager
+ *
+ * Copyright (c) 2023 Google, LLC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef LWIS_I2C_SCHED_H_
+#define LWIS_I2C_SCHED_H_
+
+#include "lwis_device.h"
+
+// Forward declaration
+struct lwis_i2c_process_queue;
+
+/* lwis_i2c_process_request:
+ * This maintains the node to identify the devices that
+ * have a request to be processed on a given I2C bus */
+struct lwis_i2c_process_request {
+ struct lwis_client *requesting_client;
+ struct list_head request_node;
+};
+
+bool lwis_i2c_process_request_queue_is_empty(struct lwis_i2c_process_queue *process_queue);
+
+void lwis_i2c_process_request_queue_initialize(struct lwis_i2c_process_queue *process_queue);
+
+void lwis_i2c_process_request_queue_destroy(struct lwis_i2c_process_queue *process_queue);
+
+#endif /* LWIS_I2C_SCHED_H_ */ \ No newline at end of file
diff --git a/lwis_ioctl.c b/lwis_ioctl.c
index 64cb1e2..b2fbe1f 100644
--- a/lwis_ioctl.c
+++ b/lwis_ioctl.c
@@ -36,6 +36,7 @@
#include "lwis_regulator.h"
#include "lwis_transaction.h"
#include "lwis_util.h"
+#include "lwis_i2c_bus_manager.h"
#define IOCTL_TO_ENUM(x) _IOC_NR(x)
#define IOCTL_ARG_SIZE(x) _IOC_SIZE(x)
@@ -189,6 +190,7 @@ static int synchronous_process_io_entries(struct lwis_device *lwis_dev, int num_
{
int ret = 0, i = 0;
+ lwis_i2c_bus_manager_lock_i2c_bus(lwis_dev);
/* Use write memory barrier at the beginning of I/O entries if the access protocol
* allows it */
if (lwis_dev->vops.register_io_barrier != NULL) {
@@ -238,6 +240,7 @@ exit:
/*use_read_barrier=*/true,
/*use_write_barrier=*/false);
}
+ lwis_i2c_bus_manager_unlock_i2c_bus(lwis_dev);
return ret;
}
@@ -1199,6 +1202,8 @@ static int construct_transaction_from_cmd(struct lwis_client *client, uint32_t c
k_transaction->resp = NULL;
k_transaction->is_weak_transaction = false;
+ k_transaction->remaining_entries_to_process = k_transaction->info.num_io_entries;
+ k_transaction->starting_read_buf = NULL;
INIT_LIST_HEAD(&k_transaction->event_list_node);
INIT_LIST_HEAD(&k_transaction->process_queue_node);
INIT_LIST_HEAD(&k_transaction->completion_fence_list);
@@ -1258,7 +1263,7 @@ static int cmd_transaction_submit(struct lwis_client *client, struct lwis_cmd_pk
ret = lwis_initialize_transaction_fences(client, k_transaction);
if (ret) {
- lwis_transaction_free(lwis_dev, k_transaction);
+ lwis_transaction_free(lwis_dev, &k_transaction);
goto err_exit;
}
@@ -1276,7 +1281,7 @@ static int cmd_transaction_submit(struct lwis_client *client, struct lwis_cmd_pk
if (ret) {
k_cmd_transaction_info_v1.info.id = LWIS_ID_INVALID;
k_cmd_transaction_info_v2.info.id = LWIS_ID_INVALID;
- lwis_transaction_free(lwis_dev, k_transaction);
+ lwis_transaction_free(lwis_dev, &k_transaction);
}
resp_header->cmd_id = header->cmd_id;
@@ -1339,7 +1344,7 @@ static int cmd_transaction_replace(struct lwis_client *client, struct lwis_cmd_p
ret = lwis_initialize_transaction_fences(client, k_transaction);
if (ret) {
- lwis_transaction_free(lwis_dev, k_transaction);
+ lwis_transaction_free(lwis_dev, &k_transaction);
goto err_exit;
}
@@ -1357,7 +1362,7 @@ static int cmd_transaction_replace(struct lwis_client *client, struct lwis_cmd_p
if (ret) {
k_cmd_transaction_info_v1.info.id = LWIS_ID_INVALID;
k_cmd_transaction_info_v2.info.id = LWIS_ID_INVALID;
- lwis_transaction_free(lwis_dev, k_transaction);
+ lwis_transaction_free(lwis_dev, &k_transaction);
}
resp_header->cmd_id = header->cmd_id;
@@ -1804,23 +1809,17 @@ static int handle_cmd_pkt(struct lwis_client *lwis_client, struct lwis_cmd_pkt *
break;
case LWIS_CMD_ID_TRANSACTION_SUBMIT:
case LWIS_CMD_ID_TRANSACTION_SUBMIT_V2:
- mutex_lock(&lwis_client->lock);
ret = cmd_transaction_submit(lwis_client, header,
(struct lwis_cmd_pkt __user *)user_msg);
- mutex_unlock(&lwis_client->lock);
break;
case LWIS_CMD_ID_TRANSACTION_CANCEL:
- mutex_lock(&lwis_client->lock);
ret = cmd_transaction_cancel(lwis_client, header,
(struct lwis_cmd_transaction_cancel __user *)user_msg);
- mutex_unlock(&lwis_client->lock);
break;
case LWIS_CMD_ID_TRANSACTION_REPLACE:
case LWIS_CMD_ID_TRANSACTION_REPLACE_V2:
- mutex_lock(&lwis_client->lock);
ret = cmd_transaction_replace(lwis_client, header,
(struct lwis_cmd_pkt __user *)user_msg);
- mutex_unlock(&lwis_client->lock);
break;
case LWIS_CMD_ID_PERIODIC_IO_SUBMIT:
mutex_lock(&lwis_client->lock);
diff --git a/lwis_periodic_io.c b/lwis_periodic_io.c
index 85229bc..39145ab 100644
--- a/lwis_periodic_io.c
+++ b/lwis_periodic_io.c
@@ -23,6 +23,7 @@
#include "lwis_ioreg.h"
#include "lwis_transaction.h"
#include "lwis_util.h"
+#include "lwis_i2c_bus_manager.h"
static enum hrtimer_restart periodic_io_timer_func(struct hrtimer *timer)
{
@@ -34,10 +35,15 @@ static enum hrtimer_restart periodic_io_timer_func(struct hrtimer *timer)
struct lwis_periodic_io_proxy *periodic_io_proxy;
struct lwis_client *client;
bool active_periodic_io_present = false;
+ struct lwis_device *lwis_dev;
+ struct lwis_i2c_bus_manager *i2c_bus_manager = NULL;
periodic_io_list = container_of(timer, struct lwis_periodic_io_list, hr_timer);
client = periodic_io_list->client;
+ lwis_dev = client->lwis_dev;
+ i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
+
/* Go through all periodic io under the chosen periodic list */
spin_lock_irqsave(&client->periodic_io_lock, flags);
list_for_each_safe (it_period, it_period_tmp, &periodic_io_list->list) {
@@ -47,7 +53,7 @@ static enum hrtimer_restart periodic_io_timer_func(struct hrtimer *timer)
client->lwis_dev, sizeof(*periodic_io_proxy), GFP_ATOMIC);
if (!periodic_io_proxy) {
/* Non-fatal, skip this period */
- pr_warn("Cannot allocate new periodic io proxy.\n");
+ dev_warn(lwis_dev->dev, "Cannot allocate new periodic io proxy.\n");
} else {
periodic_io_proxy->periodic_io = periodic_io;
list_add_tail(&periodic_io_proxy->process_queue_node,
@@ -57,8 +63,12 @@ static enum hrtimer_restart periodic_io_timer_func(struct hrtimer *timer)
}
}
if (active_periodic_io_present) {
- kthread_queue_work(&client->lwis_dev->transaction_worker,
- &client->transaction_work);
+ if (i2c_bus_manager) {
+ kthread_queue_work(&i2c_bus_manager->i2c_bus_worker, &client->i2c_work);
+ } else {
+ kthread_queue_work(&client->lwis_dev->transaction_worker,
+ &client->transaction_work);
+ }
}
spin_unlock_irqrestore(&client->periodic_io_lock, flags);
if (!active_periodic_io_present) {
@@ -89,10 +99,11 @@ static struct lwis_periodic_io_list *periodic_io_list_create_locked(struct lwis_
int64_t period_ns)
{
ktime_t ktime;
+ struct lwis_device *lwis_dev = client->lwis_dev;
struct lwis_periodic_io_list *periodic_io_list =
kmalloc(sizeof(struct lwis_periodic_io_list), GFP_ATOMIC);
if (!periodic_io_list) {
- pr_err("Cannot allocate new event list\n");
+ dev_err(lwis_dev->dev, "Cannot allocate new event list\n");
return NULL;
}
@@ -104,7 +115,7 @@ static struct lwis_periodic_io_list *periodic_io_list_create_locked(struct lwis_
* into the client timer list */
INIT_LIST_HEAD(&periodic_io_list->list);
hash_add(client->timer_list, &periodic_io_list->node, period_ns);
- pr_info("Created hrtimer with timeout time %lldns", period_ns);
+ dev_info(lwis_dev->dev, "Created hrtimer with timeout time %lldns", period_ns);
/* Initialize and start the hrtimer for this periodic io list */
hrtimer_init(&periodic_io_list->hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
@@ -184,6 +195,7 @@ static int process_io_entries(struct lwis_client *client,
}
reinit_completion(&periodic_io->io_done);
+ lwis_i2c_bus_manager_lock_i2c_bus(lwis_dev);
for (i = 0; i < info->num_io_entries; ++i) {
/* Abort if periodic io is deactivated during processing.
* Abort can only apply to <= 1 write entries to prevent partial writes,
@@ -267,6 +279,8 @@ static int process_io_entries(struct lwis_client *client,
event_push:
complete(&periodic_io->io_done);
+ lwis_i2c_bus_manager_unlock_i2c_bus(lwis_dev);
+
/* Use read memory barrier at the beginning of I/O entries if the access protocol
* allows it */
if (lwis_dev->vops.register_io_barrier != NULL) {
@@ -280,8 +294,10 @@ event_push:
* there is an error */
if (!pending_events) {
if (resp->error_code && resp->error_code != -ECANCELED) {
- pr_err("process_io_entries fails with error code %d, periodic io %lld, io_entries[%d], entry_type %d",
- resp->error_code, info->id, i, entry->type);
+ dev_info(
+ lwis_dev->dev,
+ "process_io_entries fails with error code %d, periodic io %lld, io_entries[%d], entry_type %d",
+ resp->error_code, info->id, i, entry->type);
}
return ret;
}
@@ -328,7 +344,7 @@ void lwis_process_periodic_io_in_queue(struct lwis_client *client)
/* Error indicates the cancellation of the periodic io */
if (periodic_io->resp->error_code || !periodic_io->active) {
error_code = periodic_io->resp->error_code ? periodic_io->resp->error_code :
- -ECANCELED;
+ -ECANCELED;
push_periodic_io_error_event_locked(periodic_io, error_code,
&pending_events);
} else {
@@ -507,6 +523,15 @@ int lwis_periodic_io_client_flush(struct lwis_client *client)
struct lwis_periodic_io_list *it_periodic_io_list;
unsigned long flags;
+ struct lwis_device *lwis_dev = client->lwis_dev;
+ struct lwis_i2c_bus_manager *i2c_bus_manager = NULL;
+
+ struct lwis_periodic_io *periodic_cleanup_io;
+ struct lwis_periodic_io_proxy *periodic_cleanup_io_proxy;
+ struct list_head *it_cleanup_period, *it_cleanup_period_tmp;
+
+ i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
+
/* First, cancel all timers */
hash_for_each_safe (client->timer_list, i, tmp, it_periodic_io_list, node) {
spin_lock_irqsave(&client->periodic_io_lock, flags);
@@ -521,11 +546,30 @@ int lwis_periodic_io_client_flush(struct lwis_client *client)
}
/* Wait until all workload in process queue are processed */
- if (client->lwis_dev->transaction_worker_thread) {
- kthread_flush_worker(&client->lwis_dev->transaction_worker);
+ if (i2c_bus_manager) {
+ lwis_i2c_bus_manager_flush_i2c_worker(lwis_dev);
+ } else {
+ if (client->lwis_dev->transaction_worker_thread) {
+ kthread_flush_worker(&client->lwis_dev->transaction_worker);
+ }
}
spin_lock_irqsave(&client->periodic_io_lock, flags);
+ /* Cleanup any stale entries remaining after the flush */
+ list_for_each_safe (it_cleanup_period, it_cleanup_period_tmp,
+ &client->periodic_io_process_queue) {
+ periodic_cleanup_io_proxy = list_entry(
+ it_cleanup_period, struct lwis_periodic_io_proxy, process_queue_node);
+ if (periodic_cleanup_io_proxy) {
+ periodic_cleanup_io = periodic_cleanup_io_proxy->periodic_io;
+ list_del(&periodic_cleanup_io_proxy->process_queue_node);
+ if (periodic_cleanup_io) {
+ periodic_cleanup_io->active = false;
+ }
+ lwis_allocator_free(client->lwis_dev, periodic_cleanup_io_proxy);
+ }
+ }
+
/* Release the periodic io list of from all timers */
hash_for_each_safe (client->timer_list, i, tmp, it_periodic_io_list, node) {
list_for_each_safe (it_period, it_period_tmp, &it_periodic_io_list->list) {
@@ -535,6 +579,7 @@ int lwis_periodic_io_client_flush(struct lwis_client *client)
lwis_periodic_io_free(client->lwis_dev, periodic_io);
}
}
+
spin_unlock_irqrestore(&client->periodic_io_lock, flags);
return 0;
}
@@ -548,7 +593,8 @@ int lwis_periodic_io_client_cleanup(struct lwis_client *client)
ret = lwis_periodic_io_client_flush(client);
if (ret) {
- pr_err("Failed to wait for all in-process periodic io to complete\n");
+ dev_err(client->lwis_dev->dev,
+ "Failed to wait for all in-process periodic io to complete\n");
return ret;
}
diff --git a/lwis_transaction.c b/lwis_transaction.c
index 3212623..89fa685 100644
--- a/lwis_transaction.c
+++ b/lwis_transaction.c
@@ -24,6 +24,7 @@
#include "lwis_device.h"
#include "lwis_event.h"
#include "lwis_fence.h"
+#include "lwis_i2c_bus_manager.h"
#include "lwis_io_entry.h"
#include "lwis_ioreg.h"
#include "lwis_util.h"
@@ -113,14 +114,16 @@ static void save_transaction_to_history(struct lwis_client *client,
}
}
-void lwis_transaction_free(struct lwis_device *lwis_dev, struct lwis_transaction *transaction)
+void lwis_transaction_free(struct lwis_device *lwis_dev, struct lwis_transaction **ptransaction)
{
int i;
struct lwis_fence_pending_signal *pending_fence;
struct list_head *it_fence, *it_fence_tmp;
+ struct lwis_transaction *transaction = *ptransaction;
if (transaction->is_weak_transaction) {
kfree(transaction);
+ transaction = NULL;
return;
}
@@ -146,21 +149,26 @@ void lwis_transaction_free(struct lwis_device *lwis_dev, struct lwis_transaction
}
}
lwis_allocator_free(lwis_dev, transaction->info.io_entries);
+
+ transaction->starting_read_buf = NULL;
+
if (transaction->resp) {
kfree(transaction->resp);
}
kfree(transaction);
+ *ptransaction = NULL;
}
-static int process_transaction(struct lwis_client *client, struct lwis_transaction *transaction,
+static int process_transaction(struct lwis_client *client, struct lwis_transaction **ptransaction,
struct list_head *pending_events, struct list_head *pending_fences,
- bool skip_err)
+ bool skip_err, bool check_transaction_limit)
{
int i;
int ret = 0;
int pending_status;
struct lwis_io_entry *entry = NULL;
struct lwis_device *lwis_dev = client->lwis_dev;
+ struct lwis_transaction *transaction = *ptransaction;
struct lwis_transaction_info_v2 *info = &transaction->info;
struct lwis_transaction_response_header *resp = transaction->resp;
size_t resp_size;
@@ -171,14 +179,53 @@ static int process_transaction(struct lwis_client *client, struct lwis_transacti
int64_t process_timestamp = -1;
unsigned long flags;
+ int total_number_of_entries = info->num_io_entries;
+ int max_transaction_entry_limit = lwis_dev->transaction_process_limit;
+ int remaining_entries_to_be_processed = transaction->remaining_entries_to_process;
+ int number_of_entries_to_process_in_current_run = 0;
+ int processing_start_index = 0;
+ int processing_end_index = 0;
+
+ /*
+ * Process all the transactions at once if:
+ * 1. the processing device has no limitation on the number of entries to process per transaction
+ * 2. the transaction is running in event context
+ * 3. the transaction is being called as a part of cleanup
+ * Note: For #2 and #3, this transaction will not be queued for further processing by any worker thread
+ * later and therefore all entries need to be processed in the same run
+ */
+ if ((lwis_dev->transaction_process_limit <= 0) ||
+ (transaction->info.run_in_event_context) || (skip_err == true) ||
+ (check_transaction_limit == false)) {
+ max_transaction_entry_limit = total_number_of_entries;
+ }
+
+ number_of_entries_to_process_in_current_run =
+ (remaining_entries_to_be_processed > max_transaction_entry_limit) ?
+ max_transaction_entry_limit :
+ remaining_entries_to_be_processed;
+ processing_start_index = total_number_of_entries - remaining_entries_to_be_processed;
+ processing_end_index = processing_start_index + number_of_entries_to_process_in_current_run;
+ remaining_entries_to_be_processed =
+ remaining_entries_to_be_processed - number_of_entries_to_process_in_current_run;
+
if (lwis_transaction_debug) {
process_timestamp = ktime_to_ns(lwis_get_time());
}
resp_size = sizeof(struct lwis_transaction_response_header) + resp->results_size_bytes;
read_buf = (uint8_t *)resp + sizeof(struct lwis_transaction_response_header);
+
resp->completion_index = -1;
+ /*
+ * If the starting read buffer pointer is not null then use this cached location to correctly
+ * set the read buffer for the current transaction processing run.
+ */
+ if (transaction->starting_read_buf) {
+ read_buf = transaction->starting_read_buf;
+ }
+
/* Use write memory barrier at the beginning of I/O entries if the access protocol
* allows it */
if (lwis_dev->vops.register_io_barrier != NULL) {
@@ -186,8 +233,8 @@ static int process_transaction(struct lwis_client *client, struct lwis_transacti
/*use_read_barrier=*/false,
/*use_write_barrier=*/true);
}
-
- for (i = 0; i < info->num_io_entries; ++i) {
+ lwis_i2c_bus_manager_lock_i2c_bus(lwis_dev);
+ for (i = processing_start_index; i < processing_end_index; ++i) {
entry = &info->io_entries[i];
if (entry->type == LWIS_IO_ENTRY_WRITE ||
entry->type == LWIS_IO_ENTRY_WRITE_BATCH ||
@@ -312,6 +359,7 @@ static int process_transaction(struct lwis_client *client, struct lwis_transacti
resp->completion_index = i;
}
+ lwis_i2c_bus_manager_unlock_i2c_bus(lwis_dev);
if (lwis_transaction_debug) {
process_duration_ns = ktime_to_ns(lwis_get_time() - process_timestamp);
}
@@ -322,6 +370,22 @@ static int process_transaction(struct lwis_client *client, struct lwis_transacti
lwis_dev->vops.register_io_barrier(lwis_dev, /*use_read_barrier=*/true,
/*use_write_barrier=*/false);
}
+
+ if ((remaining_entries_to_be_processed > 0) && (ret == 0)) {
+ /*
+ * If there are remaining entries to be processed in this transaction,
+ * don't delete this transaction and update the current remaining
+ * count of entries in the transaction. Stop processing further
+ * until there are no more remaining entries to be processed
+ * in the transaction.
+ */
+ spin_lock_irqsave(&client->transaction_lock, flags);
+ transaction->starting_read_buf = read_buf;
+ transaction->remaining_entries_to_process = remaining_entries_to_be_processed;
+ spin_unlock_irqrestore(&client->transaction_lock, flags);
+ return ret;
+ }
+
if (pending_events) {
lwis_pending_event_push(pending_events,
resp->error_code ? info->emit_error_event_id :
@@ -337,30 +401,53 @@ static int process_transaction(struct lwis_client *client, struct lwis_transacti
}
spin_lock_irqsave(&client->transaction_lock, flags);
+ transaction->remaining_entries_to_process = remaining_entries_to_be_processed;
+
if (pending_fences) {
/* Convert -ECANCELED error code to userspace Cancellation error code */
pending_status = resp->error_code == -ECANCELED ? 1 : resp->error_code;
lwis_pending_fences_move_all(lwis_dev, transaction, pending_fences, pending_status);
}
save_transaction_to_history(client, info, process_timestamp, process_duration_ns);
+
+ /*
+ * This check needs to be handled only for cases where we are processing
+ * the transaction based on the limit specified in the dts
+ * When the transactions are cancelled or executed in event context
+ * the limit doesn't dictate the number of entries that will be processed
+ */
+ if (check_transaction_limit) {
+ /* 1. If all of the entries are processed for a given transaction then
+ * delete the transaction from the queue and enable emit signals for
+ * pending events and fences
+ * 2. Delete transaction from the process queue after the limit is fulfilled
+ * or there is an error while processing
+ */
+ list_del(&transaction->process_queue_node);
+ }
+
if (info->trigger_event_counter == LWIS_EVENT_COUNTER_EVERY_TIME) {
- /* Only clean the transaction struct for this iteration. The
- * I/O entries are not being freed. */
+ /*
+ *Only clean the transaction struct for this iteration. The
+ * I/O entries are not being freed.
+ */
kfree(transaction->resp);
kfree(transaction);
+ transaction = NULL;
} else {
- lwis_transaction_free(lwis_dev, transaction);
+ lwis_transaction_free(lwis_dev, ptransaction);
}
spin_unlock_irqrestore(&client->transaction_lock, flags);
return ret;
}
-static void cancel_transaction(struct lwis_device *lwis_dev, struct lwis_transaction *transaction,
+static void cancel_transaction(struct lwis_device *lwis_dev, struct lwis_transaction **ptransaction,
int error_code, struct list_head *pending_events,
- struct list_head *pending_fences)
+ struct list_head *pending_fences, bool delete_pending_map_node)
{
int pending_status;
+ struct lwis_transaction *transaction = *ptransaction;
struct lwis_transaction_info_v2 *info = &transaction->info;
struct lwis_transaction_response_header resp;
resp.id = info->id;
@@ -370,7 +457,7 @@ static void cancel_transaction(struct lwis_device *lwis_dev, struct lwis_transac
resp.completion_index = -1;
if (transaction->is_weak_transaction) {
- lwis_transaction_free(lwis_dev, transaction);
+ lwis_transaction_free(lwis_dev, ptransaction);
return;
}
@@ -383,33 +470,120 @@ static void cancel_transaction(struct lwis_device *lwis_dev, struct lwis_transac
pending_status = error_code == -ECANCELED ? 1 : error_code;
lwis_pending_fences_move_all(lwis_dev, transaction, pending_fences, pending_status);
}
- lwis_transaction_free(lwis_dev, transaction);
+
+ if (delete_pending_map_node) {
+ hash_del(&transaction->pending_map_node);
+ }
+
+ lwis_transaction_free(lwis_dev, ptransaction);
}
void lwis_process_transactions_in_queue(struct lwis_client *client)
{
unsigned long flags;
+ unsigned long flush_flags;
struct list_head *it_tran, *it_tran_tmp;
struct list_head pending_events;
struct list_head pending_fences;
struct lwis_transaction *transaction;
+ struct lwis_device *lwis_dev = client->lwis_dev;
+ struct lwis_i2c_bus_manager *i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
INIT_LIST_HEAD(&pending_events);
INIT_LIST_HEAD(&pending_fences);
spin_lock_irqsave(&client->transaction_lock, flags);
list_for_each_safe (it_tran, it_tran_tmp, &client->transaction_process_queue) {
+ if (!client->is_enabled) {
+ /*
+ * If client is not enabled, then we just need to requeue
+ * the transaction until the client is enabled. This will
+ * ensure that we don't loose the submitted transactions.
+ */
+ if (lwis_transaction_debug) {
+ dev_info(client->lwis_dev->dev,
+ "Client is not ready to process transactions");
+ }
+ spin_unlock_irqrestore(&client->transaction_lock, flags);
+ spin_lock_irqsave(&client->flush_lock, flush_flags);
+ if (client->flush_state == NOT_FLUSHING) {
+ if (i2c_bus_manager) {
+ kthread_queue_work(&i2c_bus_manager->i2c_bus_worker,
+ &client->i2c_work);
+ } else {
+ kthread_queue_work(&client->lwis_dev->transaction_worker,
+ &client->transaction_work);
+ }
+ }
+ spin_unlock_irqrestore(&client->flush_lock, flush_flags);
+ return;
+ }
+
transaction = list_entry(it_tran, struct lwis_transaction, process_queue_node);
- list_del(&transaction->process_queue_node);
if (transaction->resp->error_code) {
- cancel_transaction(client->lwis_dev, transaction,
+ list_del(&transaction->process_queue_node);
+ cancel_transaction(client->lwis_dev, &transaction,
transaction->resp->error_code, &pending_events,
- &pending_fences);
+ &pending_fences, false);
} else {
spin_unlock_irqrestore(&client->transaction_lock, flags);
- process_transaction(client, transaction, &pending_events, &pending_fences,
- /*skip_err=*/false);
+ process_transaction(client, &transaction, &pending_events, &pending_fences,
+ /*skip_err=*/false, /*check_transaction_limit=*/true);
spin_lock_irqsave(&client->transaction_lock, flags);
+
+ /*
+ * Continue the loop if the transaction is complete and deleted or
+ * if the transaction exists but all the entries are processed
+ */
+ if ((transaction != NULL) &&
+ (transaction->remaining_entries_to_process > 0)) {
+ /*
+ * If the transaction exists and there are entries remaning to be processed,
+ * that would indicate the transaction processing limit has reached for this
+ * device and we stop processing its queue further
+ */
+ if (lwis_transaction_debug) {
+ dev_info(
+ client->lwis_dev->dev,
+ "Transaction processing limit reached, remaining entries to process %d\n",
+ transaction->remaining_entries_to_process);
+ }
+
+ /*
+ * Queue the remaining transaction again on the transaction worker/bus maanger worker
+ * to be processed again later if the client is not flushing
+ * If the client is flushing, cancel the remaining transaction
+ * and delete from the process queue node.
+ */
+ spin_lock_irqsave(&client->flush_lock, flush_flags);
+ if (client->flush_state == NOT_FLUSHING) {
+ if (lwis_transaction_debug) {
+ dev_info(
+ client->lwis_dev->dev,
+ "Client is not flushing, schedule the remaining work");
+ }
+
+ if (i2c_bus_manager) {
+ kthread_queue_work(&i2c_bus_manager->i2c_bus_worker,
+ &client->i2c_work);
+ } else {
+ kthread_queue_work(&client->lwis_dev->transaction_worker,
+ &client->transaction_work);
+ }
+ } else {
+ if (lwis_transaction_debug) {
+ dev_info(
+ client->lwis_dev->dev,
+ "Client is flushing, aborting the remaining transaction");
+ }
+ list_del(&transaction->process_queue_node);
+ cancel_transaction(client->lwis_dev, &transaction,
+ transaction->resp->error_code, &pending_events,
+ &pending_fences, false);
+ }
+ spin_unlock_irqrestore(&client->flush_lock, flush_flags);
+ break;
+ }
}
}
spin_unlock_irqrestore(&client->transaction_lock, flags);
@@ -452,7 +626,8 @@ static void cancel_all_transactions_in_queue_locked(struct lwis_client *client,
transaction =
list_entry(it_tran, struct lwis_transaction, process_queue_node);
list_del(&transaction->process_queue_node);
- cancel_transaction(client->lwis_dev, transaction, -ECANCELED, NULL, NULL);
+ cancel_transaction(client->lwis_dev, &transaction, -ECANCELED, NULL, NULL,
+ false);
}
}
}
@@ -465,12 +640,17 @@ int lwis_transaction_client_flush(struct lwis_client *client)
int i;
struct hlist_node *tmp;
struct lwis_transaction_event_list *it_evt_list;
+ struct lwis_device *lwis_dev = NULL;
+ struct lwis_i2c_bus_manager *i2c_bus_manager = NULL;
if (!client) {
pr_err("Client pointer cannot be NULL while flushing transactions.\n");
return -ENODEV;
}
+ lwis_dev = client->lwis_dev;
+ i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
+
spin_lock_irqsave(&client->transaction_lock, flags);
hash_for_each_safe (client->transaction_list, i, tmp, it_evt_list, node) {
if ((it_evt_list->event_id & 0xFFFF0000FFFFFFFFll) ==
@@ -480,19 +660,32 @@ int lwis_transaction_client_flush(struct lwis_client *client)
list_for_each_safe (it_tran, it_tran_tmp, &it_evt_list->list) {
transaction = list_entry(it_tran, struct lwis_transaction, event_list_node);
list_del(&transaction->event_list_node);
- cancel_transaction(client->lwis_dev, transaction, -ECANCELED, NULL, NULL);
+ cancel_transaction(client->lwis_dev, &transaction, -ECANCELED, NULL, NULL,
+ false);
}
hash_del(&it_evt_list->node);
kfree(it_evt_list);
}
hash_for_each_safe (client->pending_transactions, i, tmp, transaction, pending_map_node) {
- cancel_transaction(client->lwis_dev, transaction, -ECANCELED, NULL, NULL);
- hash_del(&transaction->pending_map_node);
+ cancel_transaction(client->lwis_dev, &transaction, -ECANCELED, NULL, NULL, true);
}
spin_unlock_irqrestore(&client->transaction_lock, flags);
- if (client->lwis_dev->transaction_worker_thread)
- kthread_flush_worker(&client->lwis_dev->transaction_worker);
+ spin_lock_irqsave(&client->flush_lock, flags);
+ client->flush_state = FLUSHING;
+ spin_unlock_irqrestore(&client->flush_lock, flags);
+
+ if (i2c_bus_manager) {
+ lwis_i2c_bus_manager_flush_i2c_worker(lwis_dev);
+ } else {
+ if (client->lwis_dev->transaction_worker_thread) {
+ kthread_flush_worker(&client->lwis_dev->transaction_worker);
+ }
+ }
+
+ spin_lock_irqsave(&client->flush_lock, flags);
+ client->flush_state = NOT_FLUSHING;
+ spin_unlock_irqrestore(&client->flush_lock, flags);
spin_lock_irqsave(&client->transaction_lock, flags);
/* The transaction queue should be empty after canceling all transactions,
@@ -530,13 +723,15 @@ int lwis_transaction_client_cleanup(struct lwis_client *client)
}
list_del(&transaction->event_list_node);
if (transaction->resp->error_code || client->lwis_dev->enabled == 0) {
- cancel_transaction(client->lwis_dev, transaction, -ECANCELED, NULL, NULL);
+ cancel_transaction(client->lwis_dev, &transaction, -ECANCELED, NULL, NULL,
+ false);
} else {
spin_unlock_irqrestore(&client->transaction_lock, flags);
- process_transaction(client, transaction,
+ process_transaction(client, &transaction,
/*pending_events=*/NULL,
/*pending_fences=*/NULL,
- /*skip_err=*/true);
+ /*skip_err=*/true,
+ /*check_transaction_limit=*/false);
spin_lock_irqsave(&client->transaction_lock, flags);
}
}
@@ -725,12 +920,18 @@ static int queue_transaction_locked(struct lwis_client *client,
{
struct lwis_transaction_event_list *event_list;
struct lwis_transaction_info_v2 *info = &transaction->info;
+ struct lwis_device *lwis_dev = client->lwis_dev;
+ struct lwis_i2c_bus_manager *i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
if (transaction->queue_immediately) {
/* Immediate trigger. */
list_add_tail(&transaction->process_queue_node, &client->transaction_process_queue);
- kthread_queue_work(&client->lwis_dev->transaction_worker,
- &client->transaction_work);
+ if (i2c_bus_manager) {
+ kthread_queue_work(&i2c_bus_manager->i2c_bus_worker, &client->i2c_work);
+ } else {
+ kthread_queue_work(&client->lwis_dev->transaction_worker,
+ &client->transaction_work);
+ }
} else if (lwis_triggered_by_condition(transaction)) {
/* Trigger by trigger conditions. */
add_pending_transaction(client, transaction);
@@ -802,6 +1003,10 @@ new_repeating_transaction_iteration(struct lwis_client *client,
memcpy(resp_buf, transaction->resp, sizeof(struct lwis_transaction_response_header));
new_instance->resp = (struct lwis_transaction_response_header *)resp_buf;
+ new_instance->is_weak_transaction = transaction->is_weak_transaction;
+ new_instance->remaining_entries_to_process = transaction->info.num_io_entries;
+ new_instance->starting_read_buf = NULL;
+
INIT_LIST_HEAD(&new_instance->event_list_node);
INIT_LIST_HEAD(&new_instance->process_queue_node);
INIT_LIST_HEAD(&new_instance->completion_fence_list);
@@ -812,13 +1017,14 @@ new_repeating_transaction_iteration(struct lwis_client *client,
static void defer_transaction_locked(struct lwis_client *client,
struct lwis_transaction *transaction,
struct list_head *pending_events,
- struct list_head *pending_fences, bool del_event_list_node)
+ struct list_head *pending_fences, bool del_event_list_node,
+ unsigned long* flags)
{
- unsigned long flags = 0;
if (del_event_list_node) {
list_del(&transaction->event_list_node);
}
+
/* I2C read/write cannot be executed in IRQ context */
if (in_irq() && client->lwis_dev->type == DEVICE_TYPE_I2C) {
list_add_tail(&transaction->process_queue_node, &client->transaction_process_queue);
@@ -826,10 +1032,10 @@ static void defer_transaction_locked(struct lwis_client *client,
}
if (transaction->info.run_in_event_context) {
- spin_unlock_irqrestore(&client->transaction_lock, flags);
- process_transaction(client, transaction, pending_events, pending_fences,
- /*skip_err=*/false);
- spin_lock_irqsave(&client->transaction_lock, flags);
+ spin_unlock_irqrestore(&client->transaction_lock, *flags);
+ process_transaction(client, &transaction, pending_events, pending_fences,
+ /*skip_err=*/false, /*check_transaction_limit=*/false);
+ spin_lock_irqsave(&client->transaction_lock, *flags);
} else {
list_add_tail(&transaction->process_queue_node, &client->transaction_process_queue);
}
@@ -845,6 +1051,8 @@ int lwis_transaction_event_trigger(struct lwis_client *client, int64_t event_id,
struct lwis_transaction *new_instance;
int64_t trigger_counter = 0;
struct list_head pending_fences;
+ struct lwis_device *lwis_dev = client->lwis_dev;
+ struct lwis_i2c_bus_manager *i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
INIT_LIST_HEAD(&pending_fences);
@@ -863,7 +1071,6 @@ int lwis_transaction_event_trigger(struct lwis_client *client, int64_t event_id,
/* Go through all transactions under the chosen event list. */
list_for_each_safe (it_tran, it_tran_tmp, &event_list->list) {
transaction = list_entry(it_tran, struct lwis_transaction, event_list_node);
-
if (transaction->is_weak_transaction) {
weak_transaction = transaction;
transaction = pending_transaction_peek(client, weak_transaction->id);
@@ -885,7 +1092,8 @@ int lwis_transaction_event_trigger(struct lwis_client *client, int64_t event_id,
hash_del(&transaction->pending_map_node);
defer_transaction_locked(client, transaction, pending_events,
&pending_fences,
- /* del_event_list_node */ false);
+ /* del_event_list_node */ false,
+ &flags);
}
continue;
}
@@ -903,7 +1111,8 @@ int lwis_transaction_event_trigger(struct lwis_client *client, int64_t event_id,
if (trigger_counter == LWIS_EVENT_COUNTER_ON_NEXT_OCCURRENCE ||
trigger_counter == event_counter) {
defer_transaction_locked(client, transaction, pending_events,
- &pending_fences, /* del_event_list_node */ true);
+ &pending_fences, /* del_event_list_node */ true,
+ &flags);
} else if (trigger_counter == LWIS_EVENT_COUNTER_EVERY_TIME) {
new_instance = new_repeating_transaction_iteration(client, transaction);
if (!new_instance) {
@@ -914,14 +1123,19 @@ int lwis_transaction_event_trigger(struct lwis_client *client, int64_t event_id,
continue;
}
defer_transaction_locked(client, new_instance, pending_events,
- &pending_fences, /* del_event_list_node */ false);
+ &pending_fences, /* del_event_list_node */ false,
+ &flags);
}
}
/* Schedule deferred transactions */
if (!list_empty(&client->transaction_process_queue)) {
- kthread_queue_work(&client->lwis_dev->transaction_worker,
- &client->transaction_work);
+ if (i2c_bus_manager) {
+ kthread_queue_work(&i2c_bus_manager->i2c_bus_worker, &client->i2c_work);
+ } else {
+ kthread_queue_work(&client->lwis_dev->transaction_worker,
+ &client->transaction_work);
+ }
}
spin_unlock_irqrestore(&client->transaction_lock, flags);
@@ -940,6 +1154,8 @@ void lwis_transaction_fence_trigger(struct lwis_client *client, struct lwis_fenc
struct list_head *it_tran, *it_tran_tmp;
struct list_head pending_events;
struct list_head pending_fences;
+ struct lwis_device *lwis_dev = client->lwis_dev;
+ struct lwis_i2c_bus_manager *i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
if (list_empty(transaction_list)) {
return;
@@ -975,9 +1191,9 @@ void lwis_transaction_fence_trigger(struct lwis_client *client, struct lwis_fenc
fence->fd, transaction->info.id);
}
} else {
- cancel_transaction(client->lwis_dev, transaction,
+ cancel_transaction(client->lwis_dev, &transaction,
-ECANCELED, &pending_events,
- &pending_fences);
+ &pending_fences, false);
}
}
}
@@ -987,8 +1203,12 @@ void lwis_transaction_fence_trigger(struct lwis_client *client, struct lwis_fenc
/* Schedule deferred transactions */
if (!list_empty(&client->transaction_process_queue)) {
- kthread_queue_work(&client->lwis_dev->transaction_worker,
- &client->transaction_work);
+ if (i2c_bus_manager) {
+ kthread_queue_work(&i2c_bus_manager->i2c_bus_worker, &client->i2c_work);
+ } else {
+ kthread_queue_work(&client->lwis_dev->transaction_worker,
+ &client->transaction_work);
+ }
}
spin_unlock_irqrestore(&client->transaction_lock, flags);
diff --git a/lwis_transaction.h b/lwis_transaction.h
index 340257a..9f91b2f 100644
--- a/lwis_transaction.h
+++ b/lwis_transaction.h
@@ -18,7 +18,8 @@ struct lwis_device;
struct lwis_client;
struct lwis_fence;
-/* Transaction entry. Each entry belongs to two queues:
+/*
+ * Transaction entry. Each entry belongs to two queues:
* 1) Event list: Transactions are sorted by event IDs. This is to search for
* the appropriate transactions to trigger.
* 2) Process queue: When it's time to process, the transaction will be put
@@ -47,6 +48,16 @@ struct lwis_transaction {
struct list_head completion_fence_list;
/* Precondition fence file pointer */
struct file *precondition_fence_fp;
+ /* If the transaction has more entries to process than the transaction_process_limit
+ for the processing device, then this will save the number of entries that are
+ remaining to be processed after a given transaction process cycle
+ */
+ int remaining_entries_to_process;
+ /* Starting read buffer pointer is set to the last read location when the transaction
+ process limit has reached. During the next run for the transaction, this pointer
+ will be referred to correctly point to the read buffer for the run.
+ */
+ uint8_t *starting_read_buf;
};
/* For debugging purposes, keeps track of the transaction information, as
@@ -81,7 +92,7 @@ void lwis_transaction_fence_trigger(struct lwis_client *client, struct lwis_fenc
int lwis_transaction_cancel(struct lwis_client *client, int64_t id);
-void lwis_transaction_free(struct lwis_device *lwis_dev, struct lwis_transaction *transaction);
+void lwis_transaction_free(struct lwis_device *lwis_dev, struct lwis_transaction **ptransaction);
/* Expects lwis_client->transaction_lock to be acquired before calling
* the following functions. */
diff --git a/lwis_util.c b/lwis_util.c
index bb6e211..5a440d2 100644
--- a/lwis_util.c
+++ b/lwis_util.c
@@ -161,4 +161,9 @@ int lwis_set_kthread_priority(struct lwis_device *lwis_dev, struct task_struct *
}
return 0;
+}
+
+bool lwis_check_device_type(struct lwis_device *lwis_dev, int32_t type)
+{
+ return ((lwis_dev) && (lwis_dev->type == type));
} \ No newline at end of file
diff --git a/lwis_util.h b/lwis_util.h
index 29dca82..a313c0c 100644
--- a/lwis_util.h
+++ b/lwis_util.h
@@ -80,4 +80,9 @@ int lwis_create_kthread_workers(struct lwis_device *lwis_dev);
*/
int lwis_set_kthread_priority(struct lwis_device *lwis_dev, struct task_struct *task, u32 priority);
+/*
+ * lwis_check_device_type: Returns true if the passed lwis_device's type is same as 'type'
+ */
+bool lwis_check_device_type(struct lwis_device *lwis_dev, int32_t type);
+
#endif // LWIS_UTIL_H_