diff options
author | PixelBot AutoMerger <android-nexus-securitybot@system.gserviceaccount.com> | 2023-12-17 18:43:17 -0800 |
---|---|---|
committer | SecurityBot <android-nexus-securitybot@system.gserviceaccount.com> | 2023-12-17 18:43:17 -0800 |
commit | 4eb15b2a3a2c4ff1b7b8c02168b22bab8958ffa3 (patch) | |
tree | cdecea5622708ec3a4564da97ebefe9037a00741 | |
parent | 45dd856fa22e9a89259e38aecf1c6dfde5450a39 (diff) | |
parent | 3a481043276aab160b88fe1ace34598df43945c8 (diff) | |
download | lwis-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-- | Kbuild | 2 | ||||
-rw-r--r-- | lwis_device.c | 27 | ||||
-rw-r--r-- | lwis_device.h | 21 | ||||
-rw-r--r-- | lwis_device_i2c.c | 23 | ||||
-rw-r--r-- | lwis_device_i2c.h | 4 | ||||
-rw-r--r-- | lwis_device_top.c | 4 | ||||
-rw-r--r-- | lwis_dt.c | 48 | ||||
-rw-r--r-- | lwis_i2c.c | 21 | ||||
-rw-r--r-- | lwis_i2c_bus_manager.c | 751 | ||||
-rw-r--r-- | lwis_i2c_bus_manager.h | 118 | ||||
-rw-r--r-- | lwis_i2c_sched.c | 62 | ||||
-rw-r--r-- | lwis_i2c_sched.h | 33 | ||||
-rw-r--r-- | lwis_ioctl.c | 19 | ||||
-rw-r--r-- | lwis_periodic_io.c | 68 | ||||
-rw-r--r-- | lwis_transaction.c | 308 | ||||
-rw-r--r-- | lwis_transaction.h | 15 | ||||
-rw-r--r-- | lwis_util.c | 5 | ||||
-rw-r--r-- | lwis_util.h | 5 |
18 files changed, 1443 insertions, 91 deletions
@@ -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 +} @@ -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; } @@ -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_ |