Re: [PATCH RFC 1/2] staging: quickstart: Add ACPI quickstart button (PNP0C32) driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi

2022. szeptember 11., vasárnap 21:49 keltezéssel, Arvid Norlander írta:

> This is loosly based on a previous staging driver that was removed. See
> links below for more info on that driver. The original commit ID was
> 0be013e3dc2ee79ffab8a438bbb4e216837e3d52.
> 
> However, here a completely different approach is taken to the user space
> API (which should solve the issues the original driver had). Each PNP0C32
> device is a button, and each such button gets a separate input device
> associated with it (instead of a shared platform input device).
> 
> The button ID (as read from ACPI method GHID) is provided via a sysfs file
> "button_id".
> 
> If the button caused a wakeup it will "latch" the "wakeup_cause" sysfs file
> to true. This can be reset by a user space process.
> 
> Link: https://marc.info/?l=linux-acpi&m=120550727131007
> Link: https://lkml.org/lkml/2010/5/28/327
> Signed-off-by: Arvid Norlander <lkml@xxxxxxxxx>
> [...]
> +#define QUICKSTART_VERSION "1.04"
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/types.h>
> +#include <linux/acpi.h>
> +#include <linux/input.h>
> +#include <linux/input/sparse-keymap.h>
> +
> +MODULE_AUTHOR("Arvid Norlander <lkml@xxxxxxxxx>");
> +MODULE_AUTHOR("Angelo Arrifano");
> +MODULE_DESCRIPTION("ACPI Direct App Launch driver");
> +MODULE_LICENSE("GPL");
> +
> +#define QUICKSTART_ACPI_DEVICE_NAME	"quickstart"
> +#define QUICKSTART_ACPI_CLASS		"quickstart"
> +#define QUICKSTART_ACPI_HID		"PNP0C32"
> +
> +/*
> + * There will be two events:
> + * 0x02 - A hot button was pressed while device was off/sleeping.
> + * 0x80 - A hot button was pressed while device was up.
> + */
> +#define QUICKSTART_EVENT_WAKE		0x02
> +#define QUICKSTART_EVENT_RUNTIME	0x80
> +
> +/*
> + * Each PNP0C32 device is an individual button. This structure
> + * keeps track of data associated with said device.
> + */
> +struct quickstart_acpi {
> +	struct acpi_device *acpi_dev;
> +	struct input_dev *input_device;
> +	struct quickstart_button *button;
> +	/* Name of button for debug messages */
> +	char *name;
> +	/* ID of button as returned by GHID */
> +	u32 id;
> +	/* Flags for cleanup */
> +	unsigned int input_registered : 1;

This member is set, but never read.


> +	unsigned int sysfs_created : 1;
> +	/* Track if a wakeup event was received */
> +	unsigned int wakeup_cause : 1;
> +	/* Name of input device */
> +	char input_name[32];
> +	/* Physical path for the input device */
> +	char phys[32];
> +};
> +
> +/*
> + * Knowing what these buttons do require system specific knowledge.
> + * This could be done by matching on DMI data in a long quirk table.
> + * However, it is easier to leave it up to user space to figure this out.
> + *
> + * Using for example udev hwdb the scancode 0x1 can be remapped suitably.
> + */
> +static const struct key_entry quickstart_keymap[] = {
> +	{ KE_KEY, 0x1, { KEY_UNKNOWN } },
> +	{ KE_END, 0 },
> +};
> +
> +static ssize_t wakeup_cause_show(struct device *dev,
> +				 struct device_attribute *attr, char *buf)
> +{
> +	struct quickstart_acpi *quickstart = dev_get_drvdata(dev);
> +
> +	return scnprintf(buf, PAGE_SIZE, "%s\n",
> +			 (quickstart->wakeup_cause ? "true" : "false"));

Please use `sysfs_emit()` preferably. And I think it would be easier to use 0/1
instead of true/false. And you could use `kstrtobool()` in the _store() function.


> +}
> +
> +static ssize_t wakeup_cause_store(struct device *dev,
> +				  struct device_attribute *attr,
> +				  const char *buf, size_t count)
> +{
> +	struct quickstart_acpi *quickstart = dev_get_drvdata(dev);
> +
> +	if (count < 2)
> +		return -EINVAL;
> +
> +	if (strncasecmp(buf, "false", 4) != 0)
> +		return -EINVAL;
> +
> +	quickstart->wakeup_cause = false;
> +	return count;
> +}
> +static DEVICE_ATTR_RW(wakeup_cause);
> +
> +static ssize_t button_id_show(struct device *dev, struct device_attribute *attr,
> +			      char *buf)
> +{
> +	struct quickstart_acpi *quickstart = dev_get_drvdata(dev);
> +
> +	return scnprintf(buf, PAGE_SIZE, "%d\n", quickstart->id);

Since it is unsigned `%u` would probably be better.


> +}
> +static DEVICE_ATTR_RO(button_id);
> +
> +/* ACPI Driver functions */
> +static void quickstart_acpi_notify(struct acpi_device *acpi_dev, u32 event)
> +{
> +	struct quickstart_acpi *quickstart = acpi_driver_data(acpi_dev);
> +
> +	if (!quickstart)
> +		return;
> +
> +	switch (event) {
> +	case QUICKSTART_EVENT_WAKE:
> +		quickstart->wakeup_cause = true;
> +		break;
> +	case QUICKSTART_EVENT_RUNTIME:
> +		if (!sparse_keymap_report_event(quickstart->input_device, 0x1,
> +						1, true)) {
> +			pr_info("Key handling error\n");
> +		}
> +		break;
> +	default:
> +		pr_err("Unexpected ACPI event notify (%u)\n", event);
> +		break;
> +	}
> +}
> +
> +/*
> + * The GHID ACPI method is used to indicate the "role" of the button.
> + * However, all the meanings of these values are vendor defined.
> + *
> + * We do however expose this value to user space.
> + */
> +static int quickstart_acpi_ghid(struct quickstart_acpi *quickstart)
> +{
> +	acpi_status status;
> +	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
> +	int ret = 0;
> +	union acpi_object *obj = NULL;
> +
> +	/*
> +	 * This returns a buffer telling the button usage ID,
> +	 * and triggers pending notify events (The ones before booting).
> +	 */
> +	status = acpi_evaluate_object(quickstart->acpi_dev->handle, "GHID",
> +				      NULL, &buffer);
> +	if (ACPI_FAILURE(status)) {
> +		pr_err("%s GHID method failed\n", quickstart->name);
> +		return -EINVAL;
> +	}
> +	obj = buffer.pointer;
> +
> +	/*
> +	 * GHID returns buffers, sanity check that is the case.
> +	 */
> +	if (obj->type != ACPI_TYPE_BUFFER) {
> +		pr_err("%s GHID did not return buffer\n", quickstart->name);
> +		return -EINVAL;

`buffer.pointer` is not freed here. Since you know the maximum size, you could
consider using an on-stack buffer.


> +	}
> +
> +	/*
> +	 * Quoting the specification:
> +	 * "The GHID method can return a BYTE, WORD, or DWORD.
> +	 *  The value must be encoded in little-endian byte
> +	 *  order (least significant byte first)."
> +	 */
> +	switch (obj->buffer.length) {
> +	case 1:
> +		quickstart->id = *(u8 *)obj->buffer.pointer;
> +		break;
> +	case 2:
> +		quickstart->id = le16_to_cpu(*(u16 *)obj->buffer.pointer);

Probably does not matter here, but I personally like to use `get_unaligned_leN()`
because those functions just always work.


> +		break;
> +	case 4:
> +		quickstart->id = le32_to_cpu(*(u32 *)obj->buffer.pointer);
> +		break;
> +	case 8:
> +		quickstart->id = le64_to_cpu(*(u64 *)obj->buffer.pointer);
> +		break;
> +	default:
> +		pr_err("%s GHID method returned buffer of unexpected length %lu\n",
> +		       quickstart->name, (unsigned long)obj->buffer.length);
> +		ret = -EINVAL;
> +		break;
> +	}
> +
> +	kfree(buffer.pointer);
> +
> +	return ret;
> +}
> +
> +static int quickstart_acpi_config(struct quickstart_acpi *quickstart)
> +{
> +	char *bid = acpi_device_bid(quickstart->acpi_dev);
> +	char *name;
> +
> +	name = kmalloc(strlen(bid) + 1, GFP_KERNEL);
> +	if (!name)
> +		return -ENOMEM;
> +
> +	quickstart->name = name;
> +	strcpy(quickstart->name, bid);

You could use `kstrdup()` here, but you could probably even use `devm_kstrdup()`
and then this function could be entirely removed.


> +
> +	return 0;
> +}
> +
> +static struct attribute *quickstart_attributes[] = {
> +	&dev_attr_wakeup_cause.attr,
> +	&dev_attr_button_id.attr,
> +	NULL,
> +};
> +
> +static const struct attribute_group quickstart_attr_group = {
> +	.attrs = quickstart_attributes,
> +};
> +
> +static int quickstart_acpi_remove(struct acpi_device *acpi_dev)
> +{
> +	struct quickstart_acpi *quickstart;
> +
> +	if (!acpi_dev)
> +		return -EINVAL;
> +
> +	quickstart = acpi_driver_data(acpi_dev);
> +	if (!quickstart)
> +		return -EINVAL;
> +
> +	if (quickstart->sysfs_created)
> +		sysfs_remove_group(&quickstart->acpi_dev->dev.kobj,
> +				   &quickstart_attr_group);
> +
> +	kfree(quickstart->name);
> +	quickstart->name = NULL;
> +
> +	kfree(quickstart);
> +
> +	return 0;
> +}
> +
> +static int quickstart_acpi_add(struct acpi_device *acpi_dev)
> +{
> +	int ret;
> +	struct quickstart_acpi *quickstart;
> +
> +	if (!acpi_dev)
> +		return -EINVAL;
> +
> +	quickstart = kzalloc(sizeof(*quickstart), GFP_KERNEL);

Have you considered `devm_kzalloc()`?


> +	if (!quickstart)
> +		return -ENOMEM;
> +
> +	/*
> +	 * This must be set early for proper cleanup on error handling path.
> +	 * After this point generic error handling can be used.
> +	 */
> +	acpi_dev->driver_data = quickstart;
> +	quickstart->acpi_dev = acpi_dev;
> +	dev_set_drvdata(&acpi_dev->dev, quickstart);
> +
> +	strcpy(acpi_device_name(acpi_dev), QUICKSTART_ACPI_DEVICE_NAME);
> +	strcpy(acpi_device_class(acpi_dev), QUICKSTART_ACPI_CLASS);
> +
> +	/* Initialize device name */
> +	ret = quickstart_acpi_config(quickstart);
> +	if (ret < 0)
> +		goto error;
> +
> +	/* Retrieve the GHID ID */
> +	ret = quickstart_acpi_ghid(quickstart);
> +	if (ret < 0)
> +		goto error;
> +
> +	/* Set up sysfs entries */
> +	ret = sysfs_create_group(&quickstart->acpi_dev->dev.kobj,
> +				 &quickstart_attr_group);

You could use `devm_device_add_group()`. And then the `sysfs_created` member
could be removed.


> +	if (ret) {
> +		quickstart->sysfs_created = 0;
> +		pr_err("Unable to setup sysfs entries\n");
> +		goto error;
> +	}
> +	quickstart->sysfs_created = !ret;
> +
> +	/* Set up input device */
> +	quickstart->input_device =
> +		devm_input_allocate_device(&quickstart->acpi_dev->dev);
> +	if (!quickstart->input_device) {
> +		ret = -ENOMEM;
> +		goto error;
> +	}
> +	ret = sparse_keymap_setup(quickstart->input_device, quickstart_keymap,
> +				  NULL);
> +	if (ret)
> +		goto error;
> +
> +	snprintf(quickstart->input_name, sizeof(quickstart->phys),
> +		 "Quickstart Button %u", quickstart->id);
> +	snprintf(quickstart->phys, sizeof(quickstart->phys),
> +		 QUICKSTART_ACPI_DEVICE_NAME "/input%u", quickstart->id);
> +
> +	quickstart->input_device->name = quickstart->input_name;
> +	quickstart->input_device->phys = quickstart->phys;
> +	quickstart->input_device->id.bustype = BUS_HOST;
> +
> +	ret = input_register_device(quickstart->input_device);
> +	if (ret) {
> +		quickstart->input_registered = 0;
> +		pr_err("Unable to register input device\n");
> +		goto error;
> +	}
> +	quickstart->input_registered = !ret;
> +
> +	return 0;
> +error:
> +	quickstart_acpi_remove(acpi_dev);
> +	return ret;
> +}
> +
> +static const struct acpi_device_id quickstart_device_ids[] = {
> +	{ QUICKSTART_ACPI_HID, 0 },
> +	{ "", 0 },
> +};
> +MODULE_DEVICE_TABLE(acpi, quickstart_device_ids);
> +
> +static struct acpi_driver quickstart_acpi_driver = {
> +	.name	= "quickstart",
> +	.owner	= THIS_MODULE,
> +	.class	= QUICKSTART_ACPI_CLASS,
> +	.ids	= quickstart_device_ids,
> +	.flags	= ACPI_DRIVER_ALL_NOTIFY_EVENTS,
> +	.ops	= {
> +		.add = quickstart_acpi_add,
> +		.remove = quickstart_acpi_remove,
> +		.notify = quickstart_acpi_notify
> +	},
> +};
> +
> +/* Module functions */
> +static void quickstart_exit(void)
> +{
> +	acpi_bus_unregister_driver(&quickstart_acpi_driver);
> +}
> +
> +static int __init quickstart_init(void)
> +{
> +	int ret;
> +
> +	/* ACPI driver register */
> +	ret = acpi_bus_register_driver(&quickstart_acpi_driver);
> +	if (ret)
> +		return ret;
> +
> +	pr_info("ACPI Direct App Launch ver %s\n", QUICKSTART_VERSION);
> +
> +	return 0;
> +}
> +
> +module_init(quickstart_init);
> +module_exit(quickstart_exit);

You could use the `module_acpi_driver()` macro to generate the init/exit methods.


> --
> 2.37.3


Regards,
Barnabás Pőcze




[Index of Archives]     [Linux Kernel Development]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux