Re: [PATCH 2/2] platform: x86: add ACPI driver for ChromeOS.

From: Enric Balletbo Serra
Date: Wed Aug 02 2017 - 04:23:45 EST


Hi Andy,

2017-08-01 17:17 GMT+02:00 Andy Shevchenko <andy.shevchenko@xxxxxxxxx>:
> On Mon, Jul 31, 2017 at 4:03 PM, Enric Balletbo i Serra
> <enric.balletbo@xxxxxxxxxxxxx> wrote:
>
>> This driver attaches to the Chromeos ACPI device and the exports the values
>> reported by the ACPI in a sysfs directory. All ACPI values are presented in
>> the string form (numbers as decimal values) and can be accessed as the
>> contents of the appropriate read only files in the sysfs directory tree
>> originating in /sys/devices/platform/chromeos_acpi.
>
> Besides what Olof mentioned in reply to cover letter this patch
> requires more clean up work.
>
> I would suggest to look how to make code looks better. Try to get what
> is done under lib/, what new %p extensions are and so on.
> Also some minor typos (comments says -1, function returns -ERRNO).
>
> Few comments below. (Not a complete review)
>
> (Tired to look to this. For PDx86 the quality of this beyond good)
>
>> @@ -0,0 +1,792 @@
>> +/*
>> + * chromeos_acpi.c - ChromeOS specific ACPI support
>
> No need to put filename here. It makes more useless effort in the
> future if, for example, file would be renamed.
>
>> + *
>
>> +static struct chromeos_acpi_dev chromeos_acpi = { };
>
> {} is redundant for global static variables.
>
>> +/*
>
> /** ?
>
> If so, read how to create a correct looking kernel doc descriptions.
>
> Otherwise "@flag - " part (if you not imply kernel doc) looks a bit confusing.
>
>> + * This function operates on legacy BIOSes which do not export VBNV element
>> + * through ACPI. These BIOSes use a fixed location in NVRAM to contain a
>> + * bitmask of known flags.
>> + *
>> + * @flag - the bitmask to set, it is the responsibility of the caller to set
>> + * the proper bits.
>> + *
>> + * returns 0 on success (is running in legacy mode and chnv is initialized) or
>
>> + * -1 otherwise.
>
> Not true.
>
>> + */
>
>
>> +static int chromeos_set_nvram_flag(u8 flag)
>> +{
>
>> + u8 cur;
>> + unsigned int index = chromeos_acpi_if_data.chnv.cad_value;
>
> Reveresed X-mas tree order.
>
>> +
>> + if (!chromeos_on_legacy_firmware())
>> + return -ENODEV;
>
> ^^^^
>
>> +
>> + cur = nvram_read_byte(index);
>> +
>
>> + if ((cur & flag) != flag)
>> + nvram_write_byte(cur | flag, index);
>
> This looks suspicious. Is flag always a one bit set?
>
>> +
>> + return 0;
>> +}
>
>> +/*
>> + * Read the nvram buffer contents into the user provided space.
>> + *
>> + * returns the number of bytes copied,
>
>> or -1 on any error.
>
> Not true!
>
>> + */
>
>> +static ssize_t chromeos_vbc_nvram_read(void *buf, size_t count)
>> +{
>> + int base, size, i;
>> +
>> + if (!chromeos_acpi_if_data.nv_base.cad_is_set ||
>> + !chromeos_acpi_if_data.nv_size.cad_is_set) {
>> + pr_err("NVRAM not configured\n");
>> + return -ENODEV;
>> + }
>> +
>> + base = chromeos_acpi_if_data.nv_base.cad_value;
>> + size = chromeos_acpi_if_data.nv_size.cad_value;
>> +
>> + if (count < size) {
>> + pr_err("not enough room to read nvram (%zd < %d)\n",
>> + count, size);
>> + return -EINVAL;
>> + }
>> +
>
>> + for (i = 0; i < size; i++)
>> + ((u8 *)buf)[i] = nvram_read_byte(base++);
>
> Perhaps provide
> nvram_read_array() ?
>
>> +
>> + return size;
>> +}
>> +
>> +static ssize_t chromeos_vbc_nvram_write(const void *buf, size_t count)
>> +{
>> + unsigned int base, size, i;
>> +
>> + if (!chromeos_acpi_if_data.nv_base.cad_is_set ||
>> + !chromeos_acpi_if_data.nv_size.cad_is_set) {
>> + pr_err("NVRAM not configured\n");
>> + return -ENODEV;
>> + }
>> +
>> + size = chromeos_acpi_if_data.nv_size.cad_value;
>> + base = chromeos_acpi_if_data.nv_base.cad_value;
>> +
>> + if (count != size) {
>> + pr_err("wrong buffer size (%zd != %d)\n", count, size);
>> + return -EINVAL;
>> + }
>> +
>
>> + for (i = 0; i < size; i++) {
>> + u8 c;
>> +
>> + c = nvram_read_byte(base + i);
>> + if (c == ((u8 *)buf)[i])
>> + continue;
>> + nvram_write_byte(((u8 *)buf)[i], base + i);
>> + }
>
> nvram_write_array() ?
>
>> +
>> + return size;
>> +}
>> +
>> +/*
>> + * To show attribute value just access the container structure's `value'
>> + * field.
>> + */
>> +static ssize_t show_acpi_attribute(struct device *dev,
>> + struct device_attribute *attr, char *buf)
>> +{
>> + struct acpi_attribute *paa;
>> +
>> + paa = container_of(attr, struct acpi_attribute, dev_attr);
>> +
>
>> + return snprintf(buf, PAGE_SIZE, paa->value);
>
> sprintf();
>
> How did you test this? Where is the format string?
>
>> +}
>
>> +/*
>
> /** ?
>
>> + * create_sysfs_attribute() create and initialize an ACPI sys fs attribute
>> + * structure.
>> + * @value: attribute value
>
>> + */
>
>> +static struct acpi_attribute *create_sysfs_attribute(char *value, char *name,
>
> Function name needs prefix, otherwise too broad.
>
>> + int count, int instance)
>
>> +{
>> + struct acpi_attribute *paa;
>> + int total_size, room_left;
>> + int value_len = strlen(value);
>> +
>> + if (!value_len)
>> + return NULL;
>> +
>
>> + value_len++; /* include the terminating zero */
>
>
>
>> + total_size = value_len + sizeof(struct acpi_attribute) +
>> + strlen(name) + 1;
>
> One line?
>
>> +
>> + if (count != 1) {
>
>> + if (count >= 1000) {
>> + pr_err("too many (%d) instances of %s.\n", count, name);
>> + return NULL;
>> + }
>
> Move this outside out the parent condition.
>
>> + /* allow up to three digits and the dot */
>> + total_size += 4;
>> + }
>
>> +
>> + paa = kzalloc(total_size, GFP_KERNEL);
>> + if (!paa)
>> + return NULL;
>> +
>> + sysfs_attr_init(&paa->dev_attr.attr);
>> + paa->dev_attr.attr.mode = 0444; /* read only */
>> + paa->dev_attr.show = show_acpi_attribute;
>
>> + paa->value = (char *)(paa + 1);
>
>> + strcpy(paa->value, value);
>> + paa->dev_attr.attr.name = paa->value + value_len;
>> +
>> + room_left = total_size - value_len -
>> + offsetof(struct acpi_attribute, value);
>> +
>
>> + if (count == 1) {
>> + snprintf((char *)paa->dev_attr.attr.name, room_left, name);
>
> "%s", name
>
>> + } else {
>> + snprintf((char *)paa->dev_attr.attr.name, room_left,
>> + "%s.%d", name, instance);
>> + }
>> +
>> + paa->next_acpi_attr = chromeos_acpi.attributes;
>> + chromeos_acpi.attributes = paa;
>> +
>> + return paa;
>> +}
>
>> +/*
>
> /** ?
>
>> + * add_sysfs_attribute() create and initialize an ACPI sys fs attribute
>> + * structure and create the attribute.
>> + * @value: attribute value
>
>> + */
>
>> +static void handle_nested_acpi_package(union acpi_object *po, char *pm,
>> + int total, int instance)
>> +{
>> + int i, size, count, j;
>> + struct acpi_attribute_group *aag;
>> +
>> + count = po->package.count;
>> +
>> + size = strlen(pm) + 1 + sizeof(struct acpi_attribute_group) +
>> + sizeof(struct attribute *) * (count + 1);
>> +
>> + if (total != 1) {
>
>> + if (total >= 1000) {
>> + pr_err("too many (%d) instances of %s\n", total, pm);
>> + return;
>> + }
>
> Move it out.
>
>> + /* allow up to three digits and the dot */
>> + size += 4;
>> + }
>
>> + aag->next_acpi_attr_group = chromeos_acpi.groups;
>> + chromeos_acpi.groups = aag->next_acpi_attr_group;
>
>> + aag->ag.attrs = (struct attribute **)(aag + 1);
>
>> + aag->ag.name = (const char *)&aag->ag.attrs[count + 1];
>> +
>> + /* room left in the buffer */
>> + size = size - (aag->ag.name - (char *)aag);
>
>> + if (total != 1)
>
> Perhaps
>
> if (total == 1)
>
> to be consistent with above code for some other stuff?
>
>> + snprintf((char *)aag->ag.name, size, "%s.%d", pm, instance);
>> + else
>> + snprintf((char *)aag->ag.name, size, "%s", pm);
>
>> + j = 0; /* attribute index */
>> + for (i = 0; i < count; i++) {
>> + union acpi_object *element = po->package.elements + i;
>
>> + int copy_size = 0;
>
> Redundant assignment.
>
>> + char attr_value[40]; /* 40 chars be enough for names */
>> + struct acpi_attribute *paa;
>> +
>> + switch (element->type) {
>> + case ACPI_TYPE_INTEGER:
>> + copy_size = snprintf(attr_value, sizeof(attr_value),
>> + "%d", (int)element->integer.value);
>> + paa = create_sysfs_attribute(attr_value, pm, count, i);
>> + break;
>> +
>> + case ACPI_TYPE_STRING:
>
>> + copy_size = min(element->string.length,
>> + (u32)(sizeof(attr_value)) - 1);
>> + memcpy(attr_value, element->string.pointer, copy_size);
>> + attr_value[copy_size] = '\0';
>
> What the problem to supply atrribute value and its length?
>
>> + paa = create_sysfs_attribute(attr_value, pm, count, i);
>> + break;
>> +
>> + default:
>> + pr_err("ignoring nested type %d\n", element->type);
>> + continue;
>> + }
>> + aag->ag.attrs[j++] = &paa->dev_attr.attr;
>> + }
>> +
>> + if (sysfs_create_group(&chromeos_acpi.p_dev->dev.kobj, &aag->ag))
>> + pr_err("failed to create group %s.%d\n", pm, instance);
>> +}
>
>> +static void maybe_export_acpi_int(const char *pm, int index,
>> + unsigned int value)
>> +{
>> + int i;
>
>
>> + struct chromeos_acpi_exported_ints {
>> + const char *acpi_name;
>> + int acpi_index;
>> + struct chromeos_acpi_datum *cad;
>> + } exported_ints[] = {
>> + { "VBNV", 0, &chromeos_acpi_if_data.nv_base },
>> + { "VBNV", 1, &chromeos_acpi_if_data.nv_size },
>> + { "CHSW", 0, &chromeos_acpi_if_data.switch_state },
>> + { "CHNV", 0, &chromeos_acpi_if_data.chnv }
>> + };
>
> Shouldn't be outside and marked as static const ?
>
>> +
>> + for (i = 0; i < ARRAY_SIZE(exported_ints); i++) {
>> + struct chromeos_acpi_exported_ints *exported_int;
>> +
>
>> + exported_int = exported_ints + i;
>
> &exported_ints[i]; ?
>
>> +
>> + if (!strncmp(pm, exported_int->acpi_name, 4) &&
>> + (exported_int->acpi_index == index)) {
>> + pr_notice("registering %s %d\n", pm, index);
>> + exported_int->cad->cad_value = value;
>> + exported_int->cad->cad_is_set = true;
>> + return;
>> + }
>> + }
>> +}
>
>> +static char *acpi_buffer_to_string(union acpi_object *element)
>
> C'mon, acpi_ is for ACPI glue layer, not for some chrome custom stuff.
>
>> +{
>> + char *base, *p;
>> + int i;
>> + unsigned int room_left;
>> + /* Include this many characters per line */
>> + unsigned int char_per_line = 16;
>> + unsigned int blob_size;
>> + unsigned int string_buffer_size;
>
> Reversed X-mas tree.
>
>> +
>> + /*
>> + * As of now the VDAT structure can supply as much as 3700 bytes. When
>> + * expressed as a hex dump it becomes 3700 * 3 + 3700/16 + .. which
>> + * clearly exceeds the maximum allowed sys fs buffer size of one page
>> + * (4k).
>> + *
>> + * What this means is that we can't keep the entire blob in one sysfs
>> + * file. Currently verified boot (the consumer of the VDAT contents)
>> + * does not care about the most of the data, so as a quick fix we will
>> + * truncate it here. Once the blob data beyond the 4K boundary is
>> + * required this approach will have to be reworked.
>> + *
>> + * TODO: Split the data into multiple VDAT instances, each
>> + * not exceeding 4K or consider exporting as a binary using
>> + * sysfs_create_bin_file().
>> + */
>> +
>> + /*
>> + * X, the maximum number of bytes which will fit into a sysfs file
>> + * (one memory page) can be derived from the following equation (where
>> + * N is number of bytes included in every hex string):
>> + *
>> + * 3X + X/N + 4 <= PAGE_SIZE.
>> + *
>> + * Solving this for X gives the following
>> + */
>> + blob_size = ((PAGE_SIZE - 4) * char_per_line) / (char_per_line * 3 + 1);
>
> Oy vey!
>
>> +
>> + if (element->buffer.length > blob_size)
>> + pr_info("truncating buffer from %d to %d\n",
>> + element->buffer.length, blob_size);
>> + else
>> + blob_size = element->buffer.length;
>> +
>> + /*
>> + * Three characters to display one byte, one newline per line, all
>> + * rounded up, plus extra newline in the end, plus terminating
>> + * zero, hence + 4
>> + */
>> + string_buffer_size = blob_size * 3 + blob_size / char_per_line + 4;
>> +
>> + p = kzalloc(string_buffer_size, GFP_KERNEL);
>> + if (!p)
>> + return NULL;
>> +
>> + base = p;
>> + room_left = string_buffer_size;
>> + for (i = 0; i < blob_size; i++) {
>> + int printed;
>> +
>> + printed = snprintf(p, room_left, " %2.2x",
>> + element->buffer.pointer[i]);
>> + room_left -= printed;
>> + p += printed;
>> + if (((i + 1) % char_per_line) == 0) {
>> + if (!room_left)
>> + break;
>> + room_left--;
>> + *p++ = '\n';
>> + }
>> + }
>> + if (room_left < 2) {
>> + pr_err("no room in the buffer\n");
>> + *p = '\0';
>> + } else {
>> + *p++ = '\n';
>> + *p++ = '\0';
>> + }
>
> hex_dump_to_buffer();
> And remove this custom crap.
>
>> + return base;
>> +}
>
>> +static void handle_acpi_package(union acpi_object *po, char *pm)
>> +{
>> + int j;
>> + int count = po->package.count;
>> +
>> + for (j = 0; j < count; j++) {
>> + union acpi_object *element = po->package.elements + j;
>> + int copy_size = 0;
>> + char attr_value[256]; /* strings could be this long */
>> +
>> + switch (element->type) {
>> + case ACPI_TYPE_INTEGER:
>> + copy_size = snprintf(attr_value, sizeof(attr_value),
>> + "%d", (int)element->integer.value);
>> + add_sysfs_attribute(attr_value, pm, count, j);
>> + maybe_export_acpi_int(pm, j, (unsigned int)
>> + element->integer.value);
>> + break;
>> +
>> + case ACPI_TYPE_STRING:
>> + copy_size = min(element->string.length,
>> + (u32)(sizeof(attr_value)) - 1);
>> + memcpy(attr_value, element->string.pointer, copy_size);
>> + attr_value[copy_size] = '\0';
>> + add_sysfs_attribute(attr_value, pm, count, j);
>> + break;
>> +
>> + case ACPI_TYPE_BUFFER: {
>> + char *buf_str;
>> +
>> + buf_str = acpi_buffer_to_string(element);
>> + if (buf_str) {
>> + add_sysfs_attribute(buf_str, pm, count, j);
>> + kfree(buf_str);
>> + }
>> + break;
>> + }
>> + case ACPI_TYPE_PACKAGE:
>> + handle_nested_acpi_package(element, pm, count, j);
>> + break;
>> +
>> + default:
>> + pr_err("ignoring type %d (%s)\n", element->type, pm);
>> + break;
>> + }
>> + }
>> +}
>> +
>> +/*
>> + * add_acpi_method() evaluate an ACPI method and create sysfs attributes.
>> + *
>> + * @device: ACPI device
>> + * @pm: name of the method to evaluate
>> + */
>> +static void add_acpi_method(struct acpi_device *device, char *pm)
>> +{
>> + acpi_status status;
>> + struct acpi_buffer output;
>> + union acpi_object *po;
>> +
>> + output.length = ACPI_ALLOCATE_BUFFER;
>> + output.pointer = NULL;
>> +
>> + status = acpi_evaluate_object(device->handle, pm, NULL, &output);
>> +
>> + if (!ACPI_SUCCESS(status)) {
>> + pr_err("failed to retrieve %s (%d)\n", pm, status);
>> + return;
>> + }
>> +
>> + po = output.pointer;
>> +
>> + if (po->type != ACPI_TYPE_PACKAGE)
>> + pr_err("%s is not a package, ignored\n", pm);
>> + else
>> + handle_acpi_package(po, pm);
>> +
>> + kfree(output.pointer);
>> +}
>> +
>
>> +static int chromeos_process_mlst(struct acpi_device *device)
>> +{
>> + acpi_status status;
>> + struct acpi_buffer output;
>> + union acpi_object *po;
>> + int j;
>> +
>> + output.length = ACPI_ALLOCATE_BUFFER;
>> + output.pointer = NULL;
>> +
>> + status = acpi_evaluate_object(device->handle, MLST_METHOD, NULL,
>> + &output);
>
>> + if (!ACPI_SUCCESS(status)) {
>
> ACPI_FAILURE()
>
>> + pr_debug("failed to retrieve MLST (%d)\n", status);
>
>> + return 1;
>
> -ENOENT?
>
>> + }
>
>> + for (j = 0; j < po->package.count; j++) {
>> + union acpi_object *element = po->package.elements + j;
>
>> + int copy_size = 0;
>
> Redundant assignment
>
>> + char method[ACPI_NAME_SIZE + 1];
>> +
>> + if (element->type == ACPI_TYPE_STRING) {
>
>> + copy_size = min_t(u32, element->string.length,
>> + ACPI_NAME_SIZE);
>> + memcpy(method, element->string.pointer, copy_size);
>> + method[copy_size] = '\0';
>
> I'm not sure I understand why do you need a copy (same to the previous
> similar piece(s))?
> Can't you supply method name + length?
>
>> + add_acpi_method(device, method);
>
> Btw, name is too broad.
>
>> + }
>> + }
>> +
>> + kfree(output.pointer);
>> +
>> + return 0;
>> +}
>
>> +static int chromeos_acpi_remove(struct acpi_device *device)
>> +{
>> + return 0;
>> +}
>
> Redundant.
>
>> +static struct acpi_driver chromeos_acpi_driver = {
>> + .name = "ChromeOS ACPI driver",
>
>> + .owner = THIS_MODULE,
>
> Do we need this?
>
>> + .ids = chromeos_device_ids,
>> + .ops = {
>> + .add = chromeos_acpi_add,
>
>> + .remove = chromeos_acpi_remove,
>
> Redundant.
>
>> + },
>> +};
>
>> +static int __init chromeos_acpi_init(void)
>> +{
>
>> + int ret = 0;
>> + acpi_status status;
>
> Should be
>
> acpi_status status;
> int ret;
>
>> + chromeos_acpi.p_dev = platform_device_register_simple("chromeos_acpi",
>> + -1, NULL, 0);
>
> -1 has a definition.
>
>> + if (IS_ERR(chromeos_acpi.p_dev)) {
>> + pr_err("unable to register platform device\n");
>> + return PTR_ERR(chromeos_acpi.p_dev);
>> + }
>> +
>> + ret = acpi_bus_register_driver(&chromeos_acpi_driver);
>> + if (ret < 0) {
>> + pr_err("failed to register driver (%d)\n", ret);
>> +
>> + platform_device_unregister(chromeos_acpi.p_dev);
>> + chromeos_acpi.p_dev = NULL;
>> + return ret;
>> + }
>> +
>
>> + pr_info("installed%s\n", chromeos_on_legacy_firmware() ?strscpy(); ?
>
>> + " (legacy mode)" : "");
>
>> +
>> + pr_info("enabling S3 USB wake\n");
>> + status = acpi_evaluate_object(NULL, "\\S3UE", NULL, NULL);
>
>> + if (!ACPI_SUCCESS(status))
>
> ACPI_FAILURE()
>
>> + pr_info("failed to enable S3 USB wake\n");
>> +
>> + return 0;
>> +}
>
> --
> With Best Regards,
> Andy Shevchenko

I'll do the cleanups you suggested, look under lib to catch more
cleanups and send a second version. Many thanks for the feedback and
your patience.

Regards,
Enric