Linux Input Archive on lore.kernel.org
 help / color / Atom feed
From: Jiada Wang <jiada_wang@mentor.com>
To: <nick@shmanahar.org>, <dmitry.torokhov@gmail.com>,
	<jikos@kernel.org>, <benjamin.tissoires@redhat.com>,
	<bsz@semihalf.com>, <rydberg@bitmath.org>
Cc: <linux-input@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
	<erosca@de.adit-jv.com>, <Andrew_Gabbasov@mentor.com>,
	<jiada_wang@mentor.com>
Subject: [PATCH v8 51/52] Input: atmel_mxt_ts: Implement synchronization during various operation
Date: Thu, 19 Mar 2020 08:00:15 -0700
Message-ID: <20200319150016.61398-52-jiada_wang@mentor.com> (raw)
In-Reply-To: <20200319150016.61398-1-jiada_wang@mentor.com>

From: Sanjeev Chugh <sanjeev_chugh@mentor.com>

There could be scope of race conditions when sysfs is being handled
and at the same time, device removal is occurring. For example,
we don't want the device removal to begin if the Atmel device
cfg update is going on or firmware update is going on. In such
cases, wait for device update to be completed before the removal
continues.

    Thread                                          Thread 2:
=========================                       =========================
mxt_update_fw_store()                           mxt_remove()
mutex_lock(&data->lock)                         ...
mxt_initialize()                                //Tries to acquire lock
  request_firmware_nowait()                     mutex_lock(&data->lock)
...                                             ==>waits for lock()
...                                             .
...                                             .
mutex_unlock(&data->lock)                       .
                                                //Gets lock and proceeds
                                                mxt_free_input_device();
                                                ...
                                                mutex_unlock(&data->lock)
                                                //Frees atmel driver data
                                                kfree(data)

If the request_firmware_nowait() completes after the driver removal,
and callback is triggered. But kernel crashes since the module is
already removed.

This commit adds state machine to serialize such scenarios.

Signed-off-by: Sanjeev Chugh <sanjeev_chugh@mentor.com>
Signed-off-by: Bhuvanesh Surachari <bhuvanesh_surachari@mentor.com>
Signed-off-by: Jiada Wang <jiada_wang@mentor.com>
---
 drivers/input/touchscreen/atmel_mxt_ts.c | 222 ++++++++++++++++++++---
 1 file changed, 196 insertions(+), 26 deletions(-)

diff --git a/drivers/input/touchscreen/atmel_mxt_ts.c b/drivers/input/touchscreen/atmel_mxt_ts.c
index 50b3cc194196..a8e80e77624d 100644
--- a/drivers/input/touchscreen/atmel_mxt_ts.c
+++ b/drivers/input/touchscreen/atmel_mxt_ts.c
@@ -224,6 +224,7 @@ enum t100_type {
 #define MXT_POWERON_DELAY	150	/* msec */
 #define MXT_BOOTLOADER_WAIT	36E5	/* 1 minute */
 #define MXT_WATCHDOG_TIMEOUT	1000	/* msec */
+#define MXT_CONFIG_TIMEOUT	36E5	/* msec */
 
 /* Command to unlock bootloader */
 #define MXT_UNLOCK_CMD_MSB	0xaa
@@ -247,6 +248,20 @@ enum t100_type {
 
 #define DEBUG_MSG_MAX		200
 
+enum device_state {
+	MXT_STATE_READY,
+	MXT_STATE_UPDATING_CONFIG,
+	MXT_STATE_UPDATING_CONFIG_ASYNC,
+	MXT_STATE_START,
+	MXT_STATE_STOP,
+	MXT_STATE_GOING_AWAY
+};
+
+enum mxt_cmd {
+	UPDATE_CFG,
+	UPDATE_FW
+};
+
 struct mxt_info {
 	u8 family_id;
 	u8 variant_id;
@@ -426,11 +441,15 @@ struct mxt_data {
 	/* Indicates whether device is in suspend */
 	bool suspended;
 
-	/* Indicates whether device is updating configuration */
-	bool updating_config;
+	struct mutex lock;
 
 	unsigned int mtu;
 	bool t25_status;
+
+	/* State handling for probe/remove, open/close and config update */
+	enum device_state e_state;
+
+	struct completion update_cfg_completion;
 };
 
 struct mxt_vb2_buffer {
@@ -1654,6 +1673,7 @@ static irqreturn_t mxt_interrupt(int irq, void *dev_id)
 	struct mxt_data *data = dev_id;
 	int ret;
 
+	mutex_lock(&data->lock);
 	data->mxt_status.intp_triggered = true;
 
 	if (data->in_bootloader) {
@@ -1681,6 +1701,8 @@ static irqreturn_t mxt_interrupt(int irq, void *dev_id)
 
 exit:
 	data->mxt_status.intp_triggered = false;
+	mutex_unlock(&data->lock);
+
 	return ret;
 }
 
@@ -2263,6 +2285,8 @@ static void mxt_free_object_table(struct mxt_data *data)
 	video_unregister_device(&data->dbg.vdev);
 	v4l2_device_unregister(&data->dbg.v4l2);
 #endif
+	mutex_lock(&data->lock);
+
 	data->object_table = NULL;
 	kfree(data->info);
 	data->info = NULL;
@@ -2292,6 +2316,8 @@ static void mxt_free_object_table(struct mxt_data *data)
 	data->T100_reportid_min = 0;
 	data->T100_reportid_max = 0;
 	data->max_reportid = 0;
+
+	mutex_unlock(&data->lock);
 }
 
 static int mxt_parse_object_table(struct mxt_data *data,
@@ -2973,8 +2999,15 @@ static int mxt_configure_objects(struct mxt_data *data,
 
 static void mxt_config_cb(const struct firmware *cfg, void *ctx)
 {
+	struct mxt_data *data = ctx;
+
 	mxt_configure_objects(ctx, cfg);
 	release_firmware(cfg);
+	complete(&data->update_cfg_completion);
+	mutex_lock(&data->lock);
+	if (data->e_state != MXT_STATE_GOING_AWAY)
+		data->e_state = MXT_STATE_READY;
+	mutex_unlock(&data->lock);
 }
 
 static int mxt_bootloader_status(struct mxt_data *data)
@@ -3090,6 +3123,15 @@ static int mxt_initialize(struct mxt_data *data)
 		goto err_free_sysfs;
 
 	if (data->cfg_name) {
+		mutex_lock(&data->lock);
+		if (data->e_state != MXT_STATE_GOING_AWAY) {
+			data->e_state = MXT_STATE_UPDATING_CONFIG_ASYNC;
+		} else {
+			mutex_unlock(&data->lock);
+			return -EBUSY;
+		}
+		reinit_completion(&data->update_cfg_completion);
+		mutex_unlock(&data->lock);
 		error = request_firmware_nowait(THIS_MODULE, true,
 						data->cfg_name,
 						&client->dev,
@@ -3869,30 +3911,58 @@ static int mxt_update_file_name(struct device *dev, char **file_name,
 	return 0;
 }
 
+static int mxt_process_operation(struct mxt_data *data,
+				 enum mxt_cmd cmd,
+				 void *cmd_data);
+
 static ssize_t mxt_update_fw_store(struct device *dev,
-					struct device_attribute *attr,
-					const char *buf, size_t count)
+				   struct device_attribute *attr,
+				   const char *buf, size_t count)
 {
 	struct mxt_data *data = dev_get_drvdata(dev);
+	char *filename = NULL;
+	int ret;
+
+	ret = mxt_update_file_name(dev, &filename, buf, count);
+	if (ret)
+		goto out;
+
+	ret = mxt_process_operation(data, UPDATE_FW, filename);
+	kfree(filename);
+
+	if (ret)
+		goto out;
+
+	return count;
+out:
+	return ret;
+}
+
+static int mxt_fw_update(struct mxt_data *data,
+			 const char *filename)
+{
+	struct device *dev = &data->client->dev;
+	unsigned int len = 0;
 	int error;
 
-	error = mxt_update_file_name(dev, &data->fw_name, buf, count);
+	len = strlen(filename);
+	error = mxt_update_file_name(dev, &data->fw_name, filename, len);
 	if (error)
 		return error;
 
 	error = mxt_load_fw(dev);
 	if (error) {
 		dev_err(dev, "The firmware update failed(%d)\n", error);
-		count = error;
-	} else {
-		dev_info(dev, "The firmware update succeeded\n");
-
-		error = mxt_initialize(data);
-		if (error)
-			return error;
+		return error;
 	}
 
-	return count;
+	error = mxt_initialize(data);
+	if (error)
+		return error;
+
+	dev_info(dev, "The firmware update succeeded\n");
+
+	return error;
 }
 
 static ssize_t mxt_update_cfg_store(struct device *dev,
@@ -3900,14 +3970,38 @@ static ssize_t mxt_update_cfg_store(struct device *dev,
 		const char *buf, size_t count)
 {
 	struct mxt_data *data = dev_get_drvdata(dev);
+	char *filename = NULL;
+	int ret;
+
+	ret = mxt_update_file_name(dev, &filename, buf, count);
+	if (ret)
+		goto out;
+
+	ret = mxt_process_operation(data, UPDATE_CFG, filename);
+	kfree(filename);
+
+	if (ret)
+		goto out;
+
+	return count;
+out:
+	return ret;
+}
+
+static int mxt_cfg_update(struct mxt_data *data,
+			  char *filename)
+{
+	struct device *dev = &data->client->dev;
 	const struct firmware *cfg;
+	unsigned int len = 0;
 	int ret;
 
-	ret = mxt_update_file_name(dev, &data->cfg_name, buf, count);
+	len = strlen(filename);
+	ret = mxt_update_file_name(dev, &data->cfg_name, filename, len);
 	if (ret)
 		return ret;
 
-	ret = request_firmware(&cfg, data->cfg_name, dev);
+	ret = request_firmware(&cfg, data->cfg_name, &data->client->dev);
 	if (ret < 0) {
 		dev_err(dev, "Failure to request config file %s\n",
 			data->cfg_name);
@@ -3915,8 +4009,6 @@ static ssize_t mxt_update_cfg_store(struct device *dev,
 		goto out;
 	}
 
-	data->updating_config = true;
-
 	mxt_free_input_device(data);
 
 	if (data->suspended) {
@@ -3932,15 +4024,8 @@ static ssize_t mxt_update_cfg_store(struct device *dev,
 	}
 
 	ret = mxt_configure_objects(data, cfg);
-	if (ret)
-		goto release;
-
-	ret = count;
-
-release:
 	release_firmware(cfg);
 out:
-	data->updating_config = false;
 	return ret;
 }
 
@@ -4204,8 +4289,17 @@ static int mxt_start(struct mxt_data *data)
 {
 	int ret = 0;
 
-	if (!data->suspended || data->in_bootloader)
+	mutex_lock(&data->lock);
+	if (!data->suspended) {
+		mutex_unlock(&data->lock);
 		return 0;
+	}
+	if (data->in_bootloader || data->e_state != MXT_STATE_READY) {
+		mutex_unlock(&data->lock);
+		return -EBUSY;
+	}
+	data->e_state = MXT_STATE_START;
+	mutex_unlock(&data->lock);
 
 	switch (data->suspend_mode) {
 	case MXT_SUSPEND_T9_CTRL:
@@ -4249,8 +4343,12 @@ static int mxt_start(struct mxt_data *data)
 		ret = mxt_acquire_irq(data);
 	}
 
+	mutex_lock(&data->lock);
 	if (!ret)
 		data->suspended = false;
+	if (data->e_state != MXT_STATE_GOING_AWAY)
+		data->e_state = MXT_STATE_READY;
+	mutex_unlock(&data->lock);
 
 	return ret;
 }
@@ -4259,8 +4357,19 @@ static int mxt_stop(struct mxt_data *data)
 {
 	int ret;
 
-	if (data->suspended || data->in_bootloader || data->updating_config)
+	mutex_lock(&data->lock);
+	if (data->suspended || data->e_state == MXT_STATE_UPDATING_CONFIG) {
+		mutex_unlock(&data->lock);
 		return 0;
+	}
+	if ((data->e_state != MXT_STATE_READY &&
+	     data->e_state != MXT_STATE_GOING_AWAY)) {
+		mutex_unlock(&data->lock);
+		return -EBUSY;
+	}
+	if (data->e_state != MXT_STATE_GOING_AWAY)
+		data->e_state = MXT_STATE_STOP;
+	mutex_unlock(&data->lock);
 
 	switch (data->suspend_mode) {
 	case MXT_SUSPEND_T9_CTRL:
@@ -4290,8 +4399,15 @@ static int mxt_stop(struct mxt_data *data)
 		break;
 	}
 
+	mutex_lock(&data->lock);
 	data->suspended = true;
+
+	if (data->e_state != MXT_STATE_GOING_AWAY)
+		data->e_state = MXT_STATE_READY;
+	mutex_unlock(&data->lock);
+
 	return 0;
+
 }
 
 static int mxt_input_open(struct input_dev *dev)
@@ -4446,12 +4562,15 @@ static int mxt_probe(struct i2c_client *client, const struct i2c_device_id *id)
 	snprintf(data->phys, sizeof(data->phys), "i2c-%u-%04x/input0",
 		 client->adapter->nr, client->addr);
 
+	mutex_init(&data->lock);
+
 	data->client = client;
 	i2c_set_clientdata(client, data);
 
 	init_completion(&data->chg_completion);
 	init_completion(&data->reset_completion);
 	init_completion(&data->crc_completion);
+	init_completion(&data->update_cfg_completion);
 	mutex_init(&data->debug_msg_lock);
 
 	data->suspend_mode = dmi_check_system(chromebook_T9_suspend_dmi) ?
@@ -4545,6 +4664,18 @@ static int mxt_remove(struct i2c_client *client)
 {
 	struct mxt_data *data = i2c_get_clientdata(client);
 
+	mutex_lock(&data->lock);
+	if (data->e_state == MXT_STATE_UPDATING_CONFIG_ASYNC ||
+	    data->e_state == MXT_STATE_UPDATING_CONFIG) {
+		data->e_state = MXT_STATE_GOING_AWAY;
+		mutex_unlock(&data->lock);
+		mxt_wait_for_completion(data, &data->update_cfg_completion,
+					MXT_CONFIG_TIMEOUT);
+	} else {
+		data->e_state = MXT_STATE_GOING_AWAY;
+		mutex_unlock(&data->lock);
+	}
+
 	disable_irq(data->irq);
 	sysfs_remove_group(&client->dev.kobj, &mxt_fw_attr_group);
 	if (data->reset_gpio) {
@@ -4605,6 +4736,45 @@ static int __maybe_unused mxt_resume(struct device *dev)
 	return ret;
 }
 
+static int mxt_process_operation(struct mxt_data *data,
+				 enum mxt_cmd cmd,
+				 void *cmd_data)
+{
+	int ret = 0;
+
+	mutex_lock(&data->lock);
+	if (data->e_state != MXT_STATE_READY) {
+		mutex_unlock(&data->lock);
+		dev_err(&data->client->dev, "Atmel touch device is shutting down\n");
+		return -EBUSY;
+	}
+	data->e_state = MXT_STATE_UPDATING_CONFIG;
+	reinit_completion(&data->update_cfg_completion);
+	mutex_unlock(&data->lock);
+
+	switch (cmd) {
+	case UPDATE_CFG:
+	case UPDATE_FW:
+		if (cmd == UPDATE_CFG)
+			ret = mxt_cfg_update(data, (char *)cmd_data);
+		else
+			ret = mxt_fw_update(data, (char *)cmd_data);
+		break;
+
+	default:
+		break;
+	}
+	mutex_lock(&data->lock);
+	if (data->e_state != MXT_STATE_UPDATING_CONFIG_ASYNC) {
+		complete(&data->update_cfg_completion);
+		if (data->e_state != MXT_STATE_GOING_AWAY)
+			data->e_state = MXT_STATE_READY;
+	}
+	mutex_unlock(&data->lock);
+
+	return ret;
+}
+
 static SIMPLE_DEV_PM_OPS(mxt_pm_ops, mxt_suspend, mxt_resume);
 
 static const struct of_device_id mxt_of_match[] = {
-- 
2.17.1


  parent reply index

Thread overview: 53+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-03-19 14:59 [PATCH v8 00/52] atmel_mxt_ts misc Jiada Wang
2020-03-19 14:59 ` [PATCH v8 01/52] Input: introduce input_mt_report_slot_inactive Jiada Wang
2020-03-19 14:59 ` [PATCH v8 02/52] Input: atmel_mxt_ts - rework sysfs init/remove Jiada Wang
2020-03-19 14:59 ` [PATCH v8 03/52] Input: atmel_mxt_ts - only read messages in mxt_acquire_irq() when necessary Jiada Wang
2020-03-19 14:59 ` [PATCH v8 04/52] Input: atmel_mxt_ts - split large i2c transfers into blocks Jiada Wang
2020-03-19 14:59 ` [PATCH v8 05/52] Input: atmel_mxt_ts - output status from T48 Noise Supression Jiada Wang
2020-03-19 14:59 ` [PATCH v8 06/52] Input: atmel_mxt_ts - output status from T42 Touch Suppression Jiada Wang
2020-03-19 14:59 ` [PATCH v8 07/52] Input: atmel_mxt_ts - implement T9 vector/orientation support Jiada Wang
2020-03-19 14:59 ` [PATCH v8 08/52] Input: atmel_mxt_ts - implement T15 Key Array support Jiada Wang
2020-03-19 14:59 ` [PATCH v8 09/52] Input: atmel_mxt_ts - handle reports from T47 Stylus object Jiada Wang
2020-03-19 14:59 ` [PATCH v8 10/52] Input: atmel_mxt_ts - implement support for T107 active stylus Jiada Wang
2020-03-19 14:59 ` [PATCH v8 11/52] Input: atmel_mxt_ts - add debug for T92 gesture and T93 touch seq msgs Jiada Wang
2020-03-19 14:59 ` [PATCH v8 12/52] Input: atmel_mxt_ts - release touch state during suspend Jiada Wang
2020-03-19 14:59 ` [PATCH v8 13/52] Input: atmel_mxt_ts - add regulator control support Jiada Wang
2020-03-19 14:59 ` [PATCH v8 14/52] dt-bindings: input: atmel: add suspend mode support Jiada Wang
2020-03-19 14:59 ` [PATCH v8 15/52] Input: atmel_mxt_ts - report failures in suspend/resume Jiada Wang
2020-03-19 14:59 ` [PATCH v8 16/52] Input: atmel_mxt_ts - allow specification of firmware file name Jiada Wang
2020-03-19 14:59 ` [PATCH v8 17/52] Input: atmel_mxt_ts - handle cfg filename via pdata/sysfs Jiada Wang
2020-03-19 14:59 ` [PATCH v8 18/52] dt-bindings: input: atmel: provide name of configuration file Jiada Wang
2020-03-19 14:59 ` [PATCH v8 19/52] Input: atmel_mxt_ts - allow input name to be specified in platform data Jiada Wang
2020-03-19 14:59 ` [PATCH v8 20/52] dt-bindings: input: atmel: support to specify input name Jiada Wang
2020-03-19 14:59 ` [PATCH v8 21/52] Input: atmel_mxt_ts - add config checksum attribute to sysfs Jiada Wang
2020-03-19 14:59 ` [PATCH v8 22/52] Input: atmel_mxt_ts - refactor firmware flash to extract context into struct Jiada Wang
2020-03-19 14:59 ` [PATCH v8 23/52] Input: atmel_mxt_ts - refactor code to enter bootloader into separate func Jiada Wang
2020-03-19 14:59 ` [PATCH v8 24/52] Input: atmel_mxt_ts - combine bootloader version query with probe Jiada Wang
2020-03-19 14:59 ` [PATCH v8 25/52] Input: atmel_mxt_ts - improve bootloader state machine handling Jiada Wang
2020-03-19 14:59 ` [PATCH v8 26/52] Input: atmel_mxt_ts - rename bl_completion to chg_completion Jiada Wang
2020-03-19 14:59 ` [PATCH v8 27/52] Input: atmel_mxt_ts - make bootloader interrupt driven Jiada Wang
2020-03-19 14:59 ` [PATCH v8 28/52] Input: atmel_mxt_ts - delay enabling IRQ when not using regulators Jiada Wang
2020-03-19 14:59 ` [PATCH v8 29/52] Input: atmel_mxt_ts - implement I2C retries Jiada Wang
2020-03-19 14:59 ` [PATCH v8 30/52] Input: atmel_mxt_ts - orientation is not present in hover Jiada Wang
2020-03-19 14:59 ` [PATCH v8 31/52] Input: atmel_mxt_ts - implement debug output for messages Jiada Wang
2020-03-19 14:59 ` [PATCH v8 32/52] Input: atmel_mxt_ts - implement improved debug message interface Jiada Wang
2020-03-19 14:59 ` [PATCH v8 33/52] Input: atmel_mxt_ts - eliminate data->raw_info_block Jiada Wang
2020-03-19 14:59 ` [PATCH v8 34/52] Input: atmel_mxt_ts - Change call-points of mxt_free_* functions Jiada Wang
2020-03-19 14:59 ` [PATCH v8 35/52] Input: atmel_mxt_ts - rely on calculated_crc rather than file config_crc Jiada Wang
2020-03-19 15:00 ` [PATCH v8 36/52] input: atmel_mxt_ts: export GPIO reset line via sysfs Jiada Wang
2020-03-19 15:00 ` [PATCH v8 37/52] input: atmel_mxt_ts: Add Missing Delay for reset handling of Atmel touch panel controller in detachable displays Jiada Wang
2020-03-19 15:00 ` [PATCH v8 38/52] Input: atmel_mxt_ts: Add support for run self-test routine Jiada Wang
2020-03-19 15:00 ` [PATCH v8 39/52] Input: atmel_mxt_ts: Limit the max bytes transferred in an i2c transaction Jiada Wang
2020-03-19 15:00 ` [PATCH v8 40/52] dt-bindings: input: atmel: support to set max bytes transferred Jiada Wang
2020-03-19 15:00 ` [PATCH v8 41/52] Input: atmel_mxt_ts: return error from mxt_process_messages_until_invalid() Jiada Wang
2020-03-19 15:00 ` [PATCH v8 42/52] Input: Atmel: improve error handling in mxt_start() Jiada Wang
2020-03-19 15:00 ` [PATCH v8 43/52] Input: Atmel: improve error handling in mxt_initialize() Jiada Wang
2020-03-19 15:00 ` [PATCH v8 44/52] Input: Atmel: improve error handling in mxt_update_cfg() Jiada Wang
2020-03-19 15:00 ` [PATCH v8 45/52] Input: Atmel: Improve error handling in mxt_initialize_input_device() Jiada Wang
2020-03-19 15:00 ` [PATCH v8 46/52] Input: Atmel: handle ReportID "0x00" while processing T5 messages Jiada Wang
2020-03-19 15:00 ` [PATCH v8 47/52] Input: Atmel: use T44 object to process " Jiada Wang
2020-03-19 15:00 ` [PATCH v8 48/52] Input: atmel_mxt_ts: use gpiod_set_value_cansleep for reset pin Jiada Wang
2020-03-19 15:00 ` [PATCH v8 49/52] input: touchscreen: atmel_mxt_ts: Added sysfs entry for touchscreen status Jiada Wang
2020-03-19 15:00 ` [PATCH v8 50/52] input: atmel_mxt_ts: added sysfs interface to update atmel T38 data Jiada Wang
2020-03-19 15:00 ` Jiada Wang [this message]
2020-03-19 15:00 ` [PATCH v8 52/52] Input: atmel_mxt_ts - Fix compilation warning Jiada Wang

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20200319150016.61398-52-jiada_wang@mentor.com \
    --to=jiada_wang@mentor.com \
    --cc=Andrew_Gabbasov@mentor.com \
    --cc=benjamin.tissoires@redhat.com \
    --cc=bsz@semihalf.com \
    --cc=dmitry.torokhov@gmail.com \
    --cc=erosca@de.adit-jv.com \
    --cc=jikos@kernel.org \
    --cc=linux-input@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=nick@shmanahar.org \
    --cc=rydberg@bitmath.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

Linux Input Archive on lore.kernel.org

Archives are clonable:
	git clone --mirror https://lore.kernel.org/linux-input/0 linux-input/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 linux-input linux-input/ https://lore.kernel.org/linux-input \
		linux-input@vger.kernel.org
	public-inbox-index linux-input

Example config snippet for mirrors

Newsgroup available over NNTP:
	nntp://nntp.lore.kernel.org/org.kernel.vger.linux-input


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git