lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:   Tue, 22 Jan 2019 16:03:52 +0100
From:   Enric Balletbo Serra <eballetbo@...il.com>
To:     Nick Crews <ncrews@...omium.org>
Cc:     linux-kernel <linux-kernel@...r.kernel.org>,
        Guenter Roeck <groeck@...omium.org>,
        Simon Glass <sjg@...omium.org>,
        Daniel Kurtz <djkurtz@...gle.com>, dlaurie@...omium.org,
        Nick Crews <ncrews@...gle.com>,
        Duncan Laurie <dlaurie@...gle.com>,
        Enric Balletbo i Serra <enric.balletbo@...labora.com>,
        Benson Leung <bleung@...omium.org>,
        Sebastian Reichel <sre@...nel.org>
Subject: Re: [PATCH v3 8/9] platform/chrome: Add peakshift and adv_batt_charging

Hi Nick,

I'd like to have some feedback from power-supply subsystem if it's possible,
so adding Sebastian. Don't forget to add him for the next versions.

Missatge de Nick Crews <ncrews@...omium.org> del dia ds., 19 de gen.
2019 a les 1:15:
>
> From: Nick Crews <ncrews@...gle.com>
>
> Create "peakshift" and "advanced_battery_charging" directories
> within the "properties" directory, and create the relevant
> attributes within these. These properties have to do with
> configuring some of the advanced power management options that
> prolong battery health and reduce energy use at peak hours
> of the day.
>
> Scheduling events uses a 24 hour clock, and only supports time
> intervals of 15 minutes. For example, to set
> advanced_battery_charging to start at 4:15pm and to last for
> 6 hours and 45 minutes, you would use the argument "16 15 6 45".
>
> > cd /sys/bus/platform/devices/GOOG000C\:00
> > cat properties/peakshift/peakshift_battery_threshold
> > 015
> [means 15 percent]
> > cat properties/peakshift/peakshift_monday
> 16 00 20 30 00 00
> [starts at 4:00 pm, ends at 8:30, charging resumes at midnight]
> > echo "16 00 20 31 00 00" > properties/peakshift/peakshift_monday
> -bash: echo: write error: Invalid argument
> > dmesg | tail -n1
> [40.34534] wilco_ec GOOG00C:00: minutes must be at the quarter hour
> > echo "16 0 20 45 0 0" > properties/peakshift/peakshift_monday
> > cat properties/peakshift/peakshift_monday
> 16 00 20 45 00 00
>
> Signed-off-by: Nick Crews <ncrews@...gle.com>
> Signed-off-by: Nick Crews <ncrews@...omium.org>
> ---
>
> Changes in v3:
> - rm some useless references to internal docs from documentation
>
> Changes in v2:
> - rm license boiler plate
> - rm "wilco_ec_adv_power - " prefix from docstring
> - Add documentation
> - make format strings in read() and store() functions static
>
>  .../ABI/testing/sysfs-platform-wilco-ec       |  88 +++
>  drivers/platform/chrome/wilco_ec/Makefile     |   3 +-
>  drivers/platform/chrome/wilco_ec/adv_power.c  | 544 ++++++++++++++++++
>  drivers/platform/chrome/wilco_ec/adv_power.h  | 183 ++++++
>  drivers/platform/chrome/wilco_ec/sysfs.c      | 104 ++++
>  5 files changed, 921 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/platform/chrome/wilco_ec/adv_power.c
>  create mode 100644 drivers/platform/chrome/wilco_ec/adv_power.h
>
> diff --git a/Documentation/ABI/testing/sysfs-platform-wilco-ec b/Documentation/ABI/testing/sysfs-platform-wilco-ec
> index aadb29a17a47..bac3340c9d90 100644
> --- a/Documentation/ABI/testing/sysfs-platform-wilco-ec
> +++ b/Documentation/ABI/testing/sysfs-platform-wilco-ec
> @@ -106,3 +106,91 @@ Description:
>
>                 Input should be parseable by kstrtobool().
>                 Output will be either "0\n" or "1\n".
> +
> +What:          /sys/bus/platform/devices/GOOG000C\:00/properties/peakshift/<day of week>
> +Date:          January 2019
> +KernelVersion: 4.19
> +Description:
> +               For each weekday a start and end time to run in Peak Shift mode
> +               can be set. During these times the system will run from the
> +               battery even if the AC is attached as long as the battery stays
> +               above the threshold specified. After the end time specified the
> +               system will run from AC if attached but will not charge the
> +               battery. The system will again function normally using AC and
> +               recharging the battery after the specified Charge Start time.
> +
> +               The input buffer must have the format "start_hr start_min end_hr
> +               end_min charge_start_hr charge_start_min" The hour fields must
> +               be in the range [0-23], and the minutes must be one of (0, 15,
> +               30, 45). The string must be parseable by sscanf() using the
> +               format string "%d %d %d %d %d %d". An example valid input is
> +               "6 15   009 45 23 0", which corresponds to 6:15, 9:45, and 23:00
> +
> +               The output buffer will be filled with the format "start_hr
> +               start_min end_hr end_min charge_start_hr charge_start_min" The
> +               hour fields will be in the range [0-23], and the minutes will be
> +               one of (0, 15, 30, 45). Each number will be zero padded to two
> +               characters. An example output is "06 15 09 45 23 00", which
> +               corresponds to 6:15, 9:45, and 23:00
> +
> +What:          /sys/bus/platform/devices/GOOG000C\:00/properties/peakshift/enable
> +Date:          January 2019
> +KernelVersion: 4.19
> +Description:
> +               Enable/disable the peakshift power management policy.
> +
> +               Input should be parseable by kstrtobool().
> +               Output will be either "0\n" or "1\n".
> +
> +What:          /sys/bus/platform/devices/GOOG000C\:00/properties/peakshift/battery_threshold
> +Date:          January 2019
> +KernelVersion: 4.19
> +Description:
> +               Read/write the battery percentage threshold for which the
> +               peakshift policy is used. The valid range is [15, 50].
> +
> +               Input should be parseable to range [15,50] by kstrtou8().
> +               Output will be two-digit ascii number in range [15, 50].
> +
> +What:          /sys/bus/platform/devices/GOOG000C\:00/properties/advanced_battery_charging/<day of week>
> +Date:          January 2019
> +KernelVersion: 4.19
> +Description:
> +               Advanced Charging Mode allows the user to maximize the battery
> +               health. In Advanced Charging Mode the system will use standard
> +               charging algorithm and other techniques during non-work hours to
> +               maximize battery health. During work hours, an express charge is
> +               used. This express charge allows the battery to be charged
> +               faster; therefore, the battery is at full charge sooner. For
> +               each day the time in which the system will be most heavily used
> +               is specified by the start time and the duration.
> +
> +               The input buffer must have the format "start_hr start_min
> +               duration_hr duration_min" The hour fields must be in the range
> +               [0-23], and the minutes must be one of (0, 15, 30, 45). The
> +               string must be parseable by sscanf() using the format string
> +               "%d %d %d %d %d %d". An example valid input is "0006 15  23 45",
> +               which corresponds to a start time of 6:15 and a duration of
> +               23:45.
> +
> +               The output buffer will be filled with the format "start_hr
> +               start_min duration_hr duration_min" The hour fields will be in
> +               the range [0-23], and the minutes will be one of (0, 15, 30,
> +               45). Each number will be zero padded to two characters. An
> +               example output is "06 15 23 45", which corresponds to a start
> +               time of 6:15 and a duration of 23:45
> +
> +What:          /sys/bus/platform/devices/GOOG000C\:00/properties/advanced_battery_charging/enable
> +Date:          January 2019
> +KernelVersion: 4.19
> +Description:
> +               Enable/disable the Advanced Battery Charging policy.
> +
> +               Input should be parseable by kstrtobool().
> +               Output will be either "0\n" or "1\n".
> +
> +What:          /sys/bus/platform/devices/GOOG000C\:00/telemetry
> +Date:          January 2019
> +KernelVersion: 4.19
> +Description:
> +               Send and receive opaque binary telemetry data to/from the EC.
> diff --git a/drivers/platform/chrome/wilco_ec/Makefile b/drivers/platform/chrome/wilco_ec/Makefile
> index a271ef497b39..23378d9469a8 100644
> --- a/drivers/platform/chrome/wilco_ec/Makefile
> +++ b/drivers/platform/chrome/wilco_ec/Makefile
> @@ -1,5 +1,6 @@
>  # SPDX-License-Identifier: GPL-2.0
>
>  wilco_ec-objs                          := core.o mailbox.o debugfs.o sysfs.o \
> -                                          legacy.o event.o properties.o
> +                                          legacy.o event.o properties.o \
> +                                          adv_power.o
>  obj-$(CONFIG_WILCO_EC)                 += wilco_ec.o
> diff --git a/drivers/platform/chrome/wilco_ec/adv_power.c b/drivers/platform/chrome/wilco_ec/adv_power.c
> new file mode 100644
> index 000000000000..a26920235294
> --- /dev/null
> +++ b/drivers/platform/chrome/wilco_ec/adv_power.c
> @@ -0,0 +1,544 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * peakshift and adv_batt_charging config of Wilco EC
> + *
> + * Copyright 2018 Google LLC
> + *
> + * Peakshift:
> + * For each weekday a start and end time to run in Peak Shift mode can be set.
> + * During these times the system will run from the battery even if the AC is
> + * attached as long as the battery stays above the threshold specified.
> + * After the end time specified the system will run from AC if attached but
> + * will not charge the battery. The system will again function normally using AC
> + * and recharging the battery after the specified Charge Start time.
> + *
> + * Advanced Charging Mode:
> + * Advanced Charging Mode allows the user to maximize the battery health.
> + * In Advanced Charging Mode the system will use standard charging algorithm and
> + * other techniques during non-work hours to maximize battery health.
> + * During work hours, an express charge is used. This express charge allows the
> + * battery to be charged faster; therefore, the battery is at
> + * full charge sooner. For each day the time in which the system will be most
> + * heavily used is specified by the start time and the duration.
> + * Please read the Common UEFI BIOS Behavioral Specification and
> + * BatMan 2 BIOS_EC Specification for more details about this feature.
> + */
> +
> +#include <linux/kobject.h>
> +#include <linux/device.h>
> +#include <linux/platform_data/wilco-ec.h>
> +
> +#include "adv_power.h"
> +#include "properties.h"
> +#include "util.h"
> +
> +struct adv_batt_charging_data {
> +       int duration_hours;
> +       int duration_minutes;
> +       int start_hours;
> +       int start_minutes;
> +};
> +
> +struct peakshift_data {
> +       int start_hours;
> +       int start_minutes;
> +       int end_hours;
> +       int end_minutes;
> +       int charge_start_hours;
> +       int charge_start_minutes;
> +};
> +
> +/**
> + * struct time_bcd_format - spec for binary coded decimal time format
> + * @hour_position: how many bits left within the byte is the hour
> + * @minute_position: how many bits left within the byte is the minute
> + *
> + * Date and hour information is passed to/from the EC using packed bytes,
> + * where each byte represents an hour and a minute that some event occurs.
> + * The minute field always happens at quarter-hour intervals, so either
> + * 0, 15, 20, or 45. This allows this info to be packed within 2 bits.
> + * Along with the 5 bits of hour info [0-23], this gives us 7 used bits
> + * within each packed byte. The annoying thing is that the PEAKSHIFT and
> + * ADVANCED_BATTERY_CHARGING properties pack these 7 bits differently,
> + * hence this struct.
> + */
> +struct time_bcd_format {
> +       u8 hour_position;
> +       u8 minute_position;
> +};
> +
> +const struct time_bcd_format PEAKSHIFT_BCD_FORMAT = {
> +                            // bit[0] is unused
> +       .hour_position = 1,  // bits[1:7]
> +       .minute_position = 6 // bits[6:8]
> +};
> +
> +const struct time_bcd_format ADV_BATT_CHARGING_BCD_FORMAT = {
> +       .minute_position = 0, // bits[0:2]
> +       .hour_position = 2    // bits[2:7]
> +                             // bit[7] is unused
> +};
> +
> +/**
> + * struct peakshift_payload - The formatted peakshift time sent/received by EC.
> + * @start_time: packed byte of hour and minute info
> + * @end_time: packed byte of hour and minute info
> + * @charge_start_time: packed byte of hour and minute info
> + * @RESERVED: an unused padding byte
> + */
> +struct peakshift_payload {
> +       u8 start_time;
> +       u8 end_time;
> +       u8 charge_start_time;
> +       u8 RESERVED;
> +} __packed;
> +
> +struct adv_batt_charging_payload {
> +       u16 RESERVED;
> +       u8 duration_time;
> +       u8 start_time;
> +} __packed;
> +
> +/**
> + * extract_quarter_hour() - Convert from literal minutes to quarter hour.
> + * @minutes: Literal minutes value. Needs to be one of {0, 15, 30, 45}
> + *
> + * Return one of {0, 1, 2, 3} for each of {0, 15, 30, 45}, or -EINVAL on error.
> + */
> +static int extract_quarter_hour(int minutes)
> +{
> +       if ((minutes < 0) || (minutes > 45) || minutes % 15)
> +               return -EINVAL;
> +       return minutes / 15;
> +}
> +
> +static int check_adv_batt_charging_data(struct device *dev,
> +                                       struct adv_batt_charging_data *data)
> +{
> +       if (data->start_hours < 0 || data->start_hours > 23) {
> +               dev_err(dev, "start_hours must be in [0-23], got %d",
> +                       data->start_hours);
> +               return -EINVAL;
> +       }
> +       if (data->duration_hours < 0 || data->duration_hours > 23) {
> +               dev_err(dev, "duration_hours must be in [0-23], got %d",
> +                       data->duration_hours);
> +               return -EINVAL;
> +       }
> +       if (data->start_minutes < 0 || data->start_minutes > 59) {
> +               dev_err(dev, "start_minutes must be in [0-59], got %d",
> +                       data->start_minutes);
> +               return -EINVAL;
> +       }
> +       if (data->duration_minutes < 0 || data->duration_minutes > 59) {
> +               dev_err(dev, "duration_minutes must be in [0-59], got %d",
> +                       data->duration_minutes);
> +               return -EINVAL;
> +       }
> +       return 0;
> +}
> +
> +static int check_peakshift_data(struct device *dev, struct peakshift_data *data)
> +{
> +       if (data->start_hours < 0 || data->start_hours > 23) {
> +               dev_err(dev, "start_hours must be in [0-23], got %d",
> +                       data->start_hours);
> +               return -EINVAL;
> +       }
> +       if (data->end_hours < 0 || data->end_hours > 23) {
> +               dev_err(dev, "end_hours must be in [0-23], got %d",
> +                       data->end_hours);
> +               return -EINVAL;
> +       }
> +       if (data->charge_start_hours < 0 || data->charge_start_hours > 23) {
> +               dev_err(dev, "charge_start_hours must be in [0-23], got %d",
> +                       data->charge_start_hours);
> +               return -EINVAL;
> +       }
> +       if (data->start_minutes < 0 || data->start_minutes > 59) {
> +               dev_err(dev, "start_minutes must be in [0-59], got %d",
> +                       data->start_minutes);
> +               return -EINVAL;
> +       }
> +       if (data->end_minutes < 0 || data->end_minutes > 59) {
> +               dev_err(dev, "end_minutes must be in [0-59], got %d",
> +                       data->end_minutes);
> +               return -EINVAL;
> +       }
> +       if (data->charge_start_minutes < 0 || data->charge_start_minutes > 59) {
> +               dev_err(dev, "charge_start_minutes must be in [0-59], got %d",
> +                       data->charge_start_minutes);
> +               return -EINVAL;
> +       }
> +       return 0;
> +}
> +
> +/**
> + * pack_field() - Pack hour and minute info into a byte.
> + *
> + * @fmt: The format for how to place the info within the byte
> + * @hours: In range [0-23]
> + * @quarter_hour: In range [0-3], representing :00, :15, :30, and :45
> + *
> + * Return the packed byte.
> + */
> +static u8 pack_field(struct time_bcd_format fmt, int hours, int quarter_hour)
> +{
> +       int result = 0;
> +
> +       result |= hours << fmt.hour_position;
> +       result |= quarter_hour << fmt.minute_position;
> +       return (u8) result;
> +}
> +
> +/**
> + * unpack_field() - Extract hour and minute info from a byte.
> + *
> + * @fmt: The format for how to place the info within the byte
> + * @field: Byte which contains the packed info
> + * @hours: The value to be filled, in [0, 24]
> + * @quarter_hour: to be filled in range [0-3], meaning :00, :15, :30, and :45
> + */
> +static void unpack_field(struct time_bcd_format fmt, u8 field, int *hours,
> +                        int *quarter_hour)
> +{
> +       *hours =        (field >> fmt.hour_position)   & 0x1f; // 00011111
> +       *quarter_hour = (field >> fmt.minute_position) & 0x03; // 00000011
> +}
> +
> +static void pack_adv_batt_charging(struct adv_batt_charging_data *data,
> +                    struct adv_batt_charging_payload *payload)
> +{
> +       payload->start_time = pack_field(ADV_BATT_CHARGING_BCD_FORMAT,
> +                                        data->start_hours,
> +                                        data->start_minutes);
> +       payload->duration_time = pack_field(ADV_BATT_CHARGING_BCD_FORMAT,
> +                                      data->duration_hours,
> +                                      data->duration_minutes);
> +}
> +
> +static void unpack_adv_batt_charging(struct adv_batt_charging_data *data,
> +                      struct adv_batt_charging_payload *payload)
> +{
> +       unpack_field(ADV_BATT_CHARGING_BCD_FORMAT, payload->start_time,
> +                    &(data->start_hours),
> +                    &(data->start_minutes));
> +       unpack_field(ADV_BATT_CHARGING_BCD_FORMAT, payload->duration_time,
> +                    &(data->duration_hours),
> +                    &(data->duration_minutes));
> +}
> +
> +static void pack_peakshift(struct peakshift_data *data,
> +                          struct peakshift_payload *payload)
> +{
> +       payload->start_time = pack_field(PEAKSHIFT_BCD_FORMAT,
> +                                        data->start_hours,
> +                                        data->start_minutes);
> +       payload->end_time = pack_field(PEAKSHIFT_BCD_FORMAT,
> +                                      data->end_hours,
> +                                      data->end_minutes);
> +       payload->charge_start_time = pack_field(PEAKSHIFT_BCD_FORMAT,
> +                                               data->charge_start_hours,
> +                                               data->charge_start_minutes);
> +}
> +
> +static void unpack_peakshift(struct peakshift_data *data,
> +                            struct peakshift_payload *payload)
> +{
> +       unpack_field(PEAKSHIFT_BCD_FORMAT, payload->start_time,
> +                    &(data->start_hours),
> +                    &(data->start_minutes));
> +       unpack_field(PEAKSHIFT_BCD_FORMAT, payload->end_time,
> +                    &(data->end_hours),
> +                    &(data->end_minutes));
> +       unpack_field(PEAKSHIFT_BCD_FORMAT, payload->charge_start_time,
> +                    &(data->charge_start_hours),
> +                    &(data->charge_start_minutes));
> +}
> +
> +ssize_t wilco_ec_peakshift_show(struct kobject *kobj,
> +                               struct kobj_attribute *attr, char *buf)
> +{
> +       struct property_attribute *prop_attr;
> +       struct device *dev;
> +       struct wilco_ec_device *ec;
> +       struct peakshift_payload payload;
> +       struct peakshift_data data;
> +       static const char FORMAT[] = "%02d %02d %02d %02d %02d %02d\n";
> +       const int OUT_LENGTH = 18; //six 2-char nums, 5 spaces, 1 newline
> +       int ret;
> +
> +       if (OUT_LENGTH + 1 > PAGE_SIZE)
> +               return -ENOBUFS; //no buffer space for message + null
> +
> +       prop_attr = container_of(attr, struct property_attribute, kobj_attr);
> +       dev = device_from_kobject(kobj);
> +       ec = dev_get_drvdata(dev);
> +
> +       /* get the raw payload of data from the EC */
> +       ret = wilco_ec_get_property(ec, prop_attr->pid, sizeof(payload),
> +                                   (u8 *) &payload);
> +       if (ret < 0) {
> +               dev_err(dev, "error in wilco_ec_mailbox()");
> +               return ret;
> +       }
> +
> +       /* unpack raw bytes, and convert quarter-hour to literal minute */
> +       unpack_peakshift(&data, &payload);
> +       data.start_minutes *= 15;
> +       data.end_minutes *= 15;
> +       data.charge_start_minutes *= 15;
> +
> +       /* Check that the EC returns good data */
> +       ret = check_peakshift_data(dev, &data);
> +       if (ret < 0) {
> +               dev_err(dev, "EC returned out of range minutes or hours");
> +               return -EBADMSG;
> +       }
> +
> +       /* Print the numbers to the string */
> +       ret = scnprintf(buf, OUT_LENGTH+1, FORMAT,
> +                       data.start_hours,
> +                       data.start_minutes,
> +                       data.end_hours,
> +                       data.end_minutes,
> +                       data.charge_start_hours,
> +                       data.charge_start_minutes);
> +       if (ret != OUT_LENGTH) {
> +               dev_err(dev, "expected to write %d chars, wrote %d", OUT_LENGTH,
> +                       ret);
> +               return -EIO;
> +       }
> +
> +       return OUT_LENGTH;
> +}
> +
> +ssize_t wilco_ec_peakshift_store(struct kobject *kobj,
> +                                struct kobj_attribute *attr, const char *buf,
> +                                size_t count)
> +{
> +       struct property_attribute *prop_attr;
> +       struct device *dev;
> +       struct wilco_ec_device *ec;
> +       struct peakshift_data data;
> +       struct peakshift_payload payload;
> +       static const char FORMAT[] = "%d %d %d %d %d %d";
> +       int ret;
> +
> +       prop_attr = container_of(attr, struct property_attribute, kobj_attr);
> +       dev = device_from_kobject(kobj);
> +       ec = dev_get_drvdata(dev);
> +
> +       /* Extract our 6 numbers from the input string */
> +       ret = sscanf(buf, FORMAT,
> +                    &data.start_hours,
> +                    &data.start_minutes,
> +                    &data.end_hours,
> +                    &data.end_minutes,
> +                    &data.charge_start_hours,
> +                    &data.charge_start_minutes);
> +       if (ret != 6) {
> +               dev_err(dev, "unable to parse '%s' into 6 integers", buf);
> +               return -EINVAL;
> +       }
> +
> +       /* Ensure the integers we parsed are valid */
> +       ret = check_peakshift_data(dev, &data);
> +       if (ret < 0)
> +               return ret;
> +
> +       /* Convert the literal minutes to which quarter hour they represent */
> +       data.start_minutes = extract_quarter_hour(data.start_minutes);
> +       if (data.start_minutes < 0)
> +               goto bad_minutes;
> +       data.end_minutes = extract_quarter_hour(data.end_minutes);
> +       if (data.end_minutes < 0)
> +               goto bad_minutes;
> +       data.charge_start_minutes = extract_quarter_hour(
> +                                               data.charge_start_minutes);
> +       if (data.charge_start_minutes < 0)
> +               goto bad_minutes;
> +
> +       /* Create the raw byte payload and send it off */
> +       pack_peakshift(&data, &payload);
> +       wilco_ec_set_property(ec, OP_SET, prop_attr->pid, sizeof(payload),
> +                             (u8 *) &payload);
> +
> +       return count;
> +
> +bad_minutes:
> +       dev_err(dev, "minutes must be at the quarter hour");
> +       return -EINVAL;
> +}
> +
> +ssize_t wilco_ec_peakshift_batt_thresh_show(struct kobject *kobj,
> +                                           struct kobj_attribute *attr,
> +                                           char *buf)
> +{
> +       struct device *dev;
> +       struct wilco_ec_device *ec;
> +       static const char FORMAT[] = "%02d\n";
> +       size_t RESULT_LENGTH = 3; /* 2-char number and newline */
> +       u8 percent;
> +       int ret;
> +
> +       dev = device_from_kobject(kobj);
> +       ec = dev_get_drvdata(dev);
> +
> +       ret = wilco_ec_get_property(ec, PID_PEAKSHIFT_BATTERY_THRESHOLD, 1,
> +                                   &percent);
> +       if (ret < 0)
> +               return ret;
> +
> +       if (percent < 15 || percent > 50) {
> +               dev_err(ec->dev, "expected 15 < percentage < 50, got %d",
> +                       percent);
> +               return -EBADMSG;
> +       }
> +
> +       scnprintf(buf, RESULT_LENGTH+1, FORMAT, percent);
> +
> +       return RESULT_LENGTH;
> +}
> +
> +ssize_t wilco_ec_peakshift_batt_thresh_store(struct kobject *kobj,
> +                                            struct kobj_attribute *attr,
> +                                            const char *buf, size_t count)
> +{
> +       struct device *dev;
> +       struct wilco_ec_device *ec;
> +       u8 DECIMAL_BASE = 10;
> +       u8 percent;
> +       int ret;
> +
> +       dev = device_from_kobject(kobj);
> +       ec = dev_get_drvdata(dev);
> +
> +
> +       ret = kstrtou8(buf, DECIMAL_BASE, &percent);
> +       if (ret) {
> +               dev_err(dev, "unable to parse '%s' to u8", buf);
> +               return ret;
> +       }
> +
> +       if (percent < 15 || percent > 50) {
> +               dev_err(dev, "require 15 < batt_thresh_percent < 50, got %d",
> +                       percent);
> +               return -EINVAL;
> +       }
> +
> +       ret = wilco_ec_set_property(ec, OP_SET, PID_PEAKSHIFT_BATTERY_THRESHOLD,
> +                                   1, &percent);
> +       if (ret < 0)
> +               return ret;
> +
> +       return count;
> +}
> +
> +ssize_t wilco_ec_abc_show(struct kobject *kobj, struct kobj_attribute *attr,
> +                         char *buf)
> +{
> +       struct property_attribute *prop_attr;
> +       struct device *dev;
> +       struct wilco_ec_device *ec;
> +       struct adv_batt_charging_payload payload;
> +       struct adv_batt_charging_data data;
> +       static const char FORMAT[] = "%02d %02d %02d %02d\n";
> +       const int OUT_LENGTH = 12; //four 2-char nums, 3 spaces, 1 newline
> +       int ret;
> +
> +       prop_attr = container_of(attr, struct property_attribute, kobj_attr);
> +       dev = device_from_kobject(kobj);
> +       ec = dev_get_drvdata(dev);
> +
> +
> +       if (OUT_LENGTH + 1 > PAGE_SIZE)
> +               return -ENOBUFS; //no buffer space for message + null
> +
> +       /* get the raw payload of data from the EC */
> +       ret = wilco_ec_get_property(ec, prop_attr->pid, sizeof(payload),
> +                                   (u8 *) &payload);
> +       if (ret < 0) {
> +               dev_err(dev, "error in wilco_ec_mailbox()");
> +               return ret;
> +       }
> +
> +       /* unpack raw bytes, and convert quarter-hour to literal minute */
> +       unpack_adv_batt_charging(&data, &payload);
> +       data.start_minutes *= 15;
> +       data.duration_minutes *= 15;
> +
> +       // /* Is this needed? can we assume the EC returns good data? */
> +       // EC is returning 00 00 27 30. Was this modified, or is EC weird
> +       // out of the box?
> +       ret = check_adv_batt_charging_data(dev, &data);
> +       if (ret < 0) {
> +               dev_err(dev, "EC returned out of range minutes or hours");
> +               return -EBADMSG;
> +       }
> +
> +       /* Print the numbers to the string */
> +       ret = scnprintf(buf, OUT_LENGTH+1, FORMAT,
> +                       data.start_hours,
> +                       data.start_minutes,
> +                       data.duration_hours,
> +                       data.duration_minutes);
> +       if (ret != OUT_LENGTH) {
> +               dev_err(dev, "expected to write %d chars, wrote %d", OUT_LENGTH,
> +                       ret);
> +               return -EIO;
> +       }
> +
> +       return OUT_LENGTH;
> +}
> +
> +ssize_t wilco_ec_abc_store(struct kobject *kobj, struct kobj_attribute *attr,
> +                          const char *buf, size_t count)
> +{
> +       struct property_attribute *prop_attr;
> +       struct device *dev;
> +       struct wilco_ec_device *ec;
> +       struct adv_batt_charging_data data;
> +       struct adv_batt_charging_payload payload;
> +       static const char FORMAT[] = "%d %d %d %d";
> +       int ret;
> +
> +       prop_attr = container_of(attr, struct property_attribute, kobj_attr);
> +       dev = device_from_kobject(kobj);
> +       ec = dev_get_drvdata(dev);
> +
> +       /* Extract our 4 numbers from the input string */
> +       ret = sscanf(buf, FORMAT,
> +                    &data.start_hours,
> +                    &data.start_minutes,
> +                    &data.duration_hours,
> +                    &data.duration_minutes);
> +       if (ret != 4) {
> +               dev_err(dev, "unable to parse '%s' into 4 integers", buf);
> +               return -EINVAL;
> +       }
> +
> +       /* Ensure the integers we parsed are valid */
> +       ret = check_adv_batt_charging_data(dev, &data);
> +       if (ret < 0)
> +               return ret;
> +
> +       /* Convert the literal minutes to which quarter hour they represent */
> +       data.start_minutes = extract_quarter_hour(data.start_minutes);
> +       if (data.start_minutes < 0)
> +               goto bad_minutes;
> +       data.duration_minutes = extract_quarter_hour(data.duration_minutes);
> +       if (data.duration_minutes < 0)
> +               goto bad_minutes;
> +
> +       /* Create the raw byte payload and send it off */
> +       pack_adv_batt_charging(&data, &payload);
> +       wilco_ec_set_property(ec, OP_SET, prop_attr->pid, sizeof(payload),
> +                             (u8 *) &payload);
> +
> +       return count;
> +
> +bad_minutes:
> +       dev_err(dev, "minutes must be at the quarter hour");
> +       return -EINVAL;
> +}
> diff --git a/drivers/platform/chrome/wilco_ec/adv_power.h b/drivers/platform/chrome/wilco_ec/adv_power.h
> new file mode 100644
> index 000000000000..6fd64c6dac8f
> --- /dev/null
> +++ b/drivers/platform/chrome/wilco_ec/adv_power.h
> @@ -0,0 +1,183 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * peakshift and adv_batt_charging config of Wilco EC
> + *
> + * Copyright 2018 Google LLC
> + *
> + * Peakshift:
> + * For each weekday a start and end time to run in Peak Shift mode can be set.
> + * During these times the system will run from the battery even if the AC is
> + * attached as long as the battery stays above the threshold specified.
> + * After the end time specified the system will run from AC if attached but
> + * will not charge the battery. The system will again function normally using AC
> + * and recharging the battery after the specified Charge Start time.
> + *
> + * Advanced Charging Mode:
> + * Advanced Charging Mode allows the user to maximize the battery health.
> + * In Advanced Charging Mode the system will use standard charging algorithm and
> + * other techniques during non-work hours to maximize battery health.
> + * During work hours, an express charge is used. This express charge allows the
> + * battery to be charged faster; therefore, the battery is at
> + * full charge sooner. For each day the time in which the system will be most
> + * heavily used is specified by the start time and the duration.
> + * Please read the Common UEFI BIOS Behavioral Specification and
> + * BatMan 2 BIOS_EC Specification for more details about this feature.
> + */
> +
> +#ifndef WILCO_EC_ADV_POWER_H
> +#define WILCO_EC_ADV_POWER_H
> +
> +#include <linux/kobject.h>
> +#include <linux/platform_data/wilco-ec.h>
> +#include <linux/sysfs.h>
> +
> +#include "util.h"
> +#include "properties.h"
> +
> +#define PID_PEAKSHIFT                          0x0412
> +#define PID_PEAKSHIFT_BATTERY_THRESHOLD                0x04EB
> +#define PID_PEAKSHIFT_SUNDAY_HOURS             0x04F5
> +#define PID_PEAKSHIFT_MONDAY_HOURS             0x04F6
> +#define PID_PEAKSHIFT_TUESDAY_HOURS            0x04F7
> +#define PID_PEAKSHIFT_WEDNESDAY_HOURS          0x04F8
> +#define PID_PEAKSHIFT_THURSDAY_HOURS           0x04F9
> +#define PID_PEAKSHIFT_FRIDAY_HOURS             0x04Fa
> +#define PID_PEAKSHIFT_SATURDAY_HOURS           0x04Fb
> +
> +#define PID_ABC_MODE                           0x04ed
> +#define PID_ABC_SUNDAY_HOURS                   0x04F5
> +#define PID_ABC_MONDAY_HOURS                   0x04F6
> +#define PID_ABC_TUESDAY_HOURS                  0x04F7
> +#define PID_ABC_WEDNESDAY_HOURS                        0x04F8
> +#define PID_ABC_THURSDAY_HOURS                 0x04F9
> +#define PID_ABC_FRIDAY_HOURS                   0x04FA
> +#define PID_ABC_SATURDAY_HOURS                 0x04FB
> +
> +/**
> + * wilco_ec_peakshift_show() - Retrieves times stored for the peakshift policy.
> + * @kobj: kobject representing the directory this attribute is in
> + * @attr: Attribute stored within the proper property_attribute
> + * @buf: Output buffer to fill with the result
> + *
> + * The output buffer will be filled with the format
> + * "start_hr start_min end_hr end_min charge_start_hr charge_start_min"
> + * The hour fields will be in the range [0-23], and the minutes will be
> + * one of (0, 15, 30, 45). Each number will be zero padded to two characters.
> + *
> + * An example output is "06 15 09 45 23 00",
> + * which corresponds to 6:15, 9:45, and 23:00
> + *
> + * Return the length of the output buffer, or negative error code on failure.
> + */
> +ssize_t wilco_ec_peakshift_show(struct kobject *kobj,
> +                               struct kobj_attribute *attr, char *buf);
> +
> +/**
> + * wilco_ec_peakshift_store() - Saves times for the peakshift policy.
> + * @kobj: kobject representing the directory this attribute is in
> + * @attr: Attribute stored within the proper property_attribute
> + * @buf: Raw input buffer
> + * @count: Number of bytes in input buffer
> + *
> + * The input buffer must have the format
> + * "start_hr start_min end_hr end_min charge_start_hr charge_start_min"
> + * The hour fields must be in the range [0-23], and the minutes must be
> + * one of (0, 15, 30, 45). The string must be parseable by sscanf() using the
> + * format string "%d %d %d %d %d %d".
> + *
> + * An example valid input is "6 15     009 45 23 0",
> + * which corresponds to 6:15, 9:45, and 23:00
> + *
> + * Return number of bytes consumed from input, negative error code on failure.
> + */
> +ssize_t wilco_ec_peakshift_store(struct kobject *kobj,
> +                                struct kobj_attribute *attr, const char *buf,
> +                                size_t count);
> +
> +/**
> + * peakshift_battery_show() - Retrieve batt percentage at which peakshift stops
> + * @kobj: kobject representing the directory this attribute is in
> + * @attr: Attribute stored within the proper property_attribute
> + * @buf: Output buffer to fill with the result
> + *
> + * Result will be a 2 character integer representing the
> + * battery percentage at which peakshift stops. Will be in range [15, 50].
> + *
> + * Return the length of the output buffer, or negative error code on failure.
> + */
> +ssize_t wilco_ec_peakshift_batt_thresh_show(struct kobject *kobj,
> +                                           struct kobj_attribute *attr,
> +                                           char *buf);
> +
> +/**
> + * peakshift_battery_store() - Save batt percentage at which peakshift stops
> + * @kobj: kobject representing the directory this attribute is in
> + * @attr: Attribute stored within the proper property_attribute
> + * @buf: Input buffer, should be parseable to range [15,50] by kstrtou8()
> + * @count: Number of bytes in input buffer
> + *
> + * Return number of bytes consumed from input, negative error code on failure.
> + */
> +ssize_t wilco_ec_peakshift_batt_thresh_store(struct kobject *kobj,
> +                                            struct kobj_attribute *attr,
> +                                            const char *buf, size_t count);
> +
> +/**
> + * wilco_ec_abc_show() - Retrieve times for Advanced Battery Charging
> + * @kobj: kobject representing the directory this attribute is in
> + * @attr: Attribute stored within the proper property_attribute
> + * @buf: Output buffer to fill with the result
> + *
> + * The output buffer will be filled with the format
> + * "start_hr start_min duration_hr duration_min"
> + * The hour fields will be in the range [0-23], and the minutes will be
> + * one of (0, 15, 30, 45). Each number will be zero padded to two characters.
> + *
> + * An example output is "06 15 23 45",
> + * which corresponds to a start time of 6:15 and a duration of 23:45
> + *
> + * Return the length of the output buffer, or negative error code on failure.
> + */
> +ssize_t wilco_ec_abc_show(struct kobject *kobj, struct kobj_attribute *attr,
> +                         char *buf);
> +
> +/**
> + * wilco_ec_abc_store() - Save times for Advanced Battery Charging
> + * @kobj: kobject representing the directory this attribute is in
> + * @attr: Attribute stored within the proper property_attribute
> + * @buf: The raw input buffer
> + * @count: Number of bytes in input buffer
> + *
> + * The input buffer must have the format
> + * "start_hr start_min duration_hr duration_min"
> + * The hour fields must be in the range [0-23], and the minutes must be
> + * one of (0, 15, 30, 45). The string must be parseable by sscanf() using the
> + * format string "%d %d %d %d %d %d".
> + *
> + * An example valid input is "0006 15     23 45",
> + * which corresponds to a start time of 6:15 and a duration of 23:45
> + *
> + * Return number of bytes consumed, or negative error code on failure.
> + */
> +ssize_t wilco_ec_abc_store(struct kobject *kobj, struct kobj_attribute *attr,
> +                          const char *buf, size_t count);
> +
> +#define PEAKSHIFT_KOBJ_ATTR(_name)                                     \
> +__ATTR(_name, 0644, wilco_ec_peakshift_show, wilco_ec_peakshift_store)
> +
> +#define PEAKSHIFT_ATTRIBUTE(_var, _name, _pid)                         \
> +struct property_attribute _var = {                                     \
> +       .kobj_attr = PEAKSHIFT_KOBJ_ATTR(_name),                        \
> +       .pid = _pid,                                                    \
> +}
> +
> +#define ABC_KOBJ_ATTR(_name)                                           \
> +__ATTR(_name, 0644, wilco_ec_abc_show, wilco_ec_abc_store)
> +
> +#define ABC_ATTRIBUTE(_var, _name, _pid)                               \
> +struct property_attribute _var = {                                     \
> +       .kobj_attr = ABC_KOBJ_ATTR(_name),                              \
> +       .pid = _pid,                                                    \
> +}
> +
> +#endif /* WILCO_EC_ADV_POWER_H */
> diff --git a/drivers/platform/chrome/wilco_ec/sysfs.c b/drivers/platform/chrome/wilco_ec/sysfs.c
> index ca2156b7e08e..83de0daef265 100644
> --- a/drivers/platform/chrome/wilco_ec/sysfs.c
> +++ b/drivers/platform/chrome/wilco_ec/sysfs.c
> @@ -16,6 +16,7 @@
>
>  #include "legacy.h"
>  #include "properties.h"
> +#include "adv_power.h"
>
>  #define WILCO_EC_ATTR_RO(_name)                                                \
>  __ATTR(_name, 0444, wilco_ec_##_name##_show, NULL)
> @@ -81,6 +82,67 @@ struct attribute *wilco_ec_property_attrs[] = {
>  ATTRIBUTE_GROUPS(wilco_ec_property);
>  struct kobject *prop_dir_kobj;
>
> +/* Make peakshift attrs, which live inside GOOG000C:00/properties/peakshift */
> +struct kobj_attribute kobj_attr_peakshift_battery_threshold =
> +       __ATTR(battery_threshold, 0644, wilco_ec_peakshift_batt_thresh_show,
> +              wilco_ec_peakshift_batt_thresh_store);
> +BOOLEAN_PROPERTY_RW_ATTRIBUTE(OP_SET, prop_attr_peakshift, enable,
> +                             PID_PEAKSHIFT);
> +PEAKSHIFT_ATTRIBUTE(prop_attr_peakshift_sunday, sunday,
> +                   PID_PEAKSHIFT_SUNDAY_HOURS);
> +PEAKSHIFT_ATTRIBUTE(prop_attr_peakshift_monday, monday,
> +                   PID_PEAKSHIFT_MONDAY_HOURS);
> +PEAKSHIFT_ATTRIBUTE(prop_attr_peakshift_tuesday, tuesday,
> +                   PID_PEAKSHIFT_TUESDAY_HOURS);
> +PEAKSHIFT_ATTRIBUTE(prop_attr_peakshift_wednesday, wednesday,
> +                   PID_PEAKSHIFT_WEDNESDAY_HOURS);
> +PEAKSHIFT_ATTRIBUTE(prop_attr_peakshift_thursday, thursday,
> +                   PID_PEAKSHIFT_THURSDAY_HOURS);
> +PEAKSHIFT_ATTRIBUTE(prop_attr_peakshift_friday, friday,
> +                   PID_PEAKSHIFT_FRIDAY_HOURS);
> +PEAKSHIFT_ATTRIBUTE(prop_attr_peakshift_saturday, saturday,
> +                   PID_PEAKSHIFT_SATURDAY_HOURS);
> +struct attribute *wilco_ec_peakshift_attrs[] = {
> +       &kobj_attr_peakshift_battery_threshold.attr,
> +       &prop_attr_peakshift.kobj_attr.attr,
> +       &prop_attr_peakshift_sunday.kobj_attr.attr,
> +       &prop_attr_peakshift_monday.kobj_attr.attr,
> +       &prop_attr_peakshift_tuesday.kobj_attr.attr,
> +       &prop_attr_peakshift_wednesday.kobj_attr.attr,
> +       &prop_attr_peakshift_thursday.kobj_attr.attr,
> +       &prop_attr_peakshift_friday.kobj_attr.attr,
> +       &prop_attr_peakshift_saturday.kobj_attr.attr,
> +       NULL
> +};
> +ATTRIBUTE_GROUPS(wilco_ec_peakshift);
> +struct kobject *peakshift_dir_kobj;
> +
> +/**
> + * Make peakshift attrs, which live inside
> + * GOOG000C:00/properties/advanced_battery_charging
> + */
> +BOOLEAN_PROPERTY_RW_ATTRIBUTE(OP_SET, prop_attr_abc, enable, PID_ABC_MODE);
> +ABC_ATTRIBUTE(prop_attr_abc_sunday, sunday, PID_ABC_SUNDAY_HOURS);
> +ABC_ATTRIBUTE(prop_attr_abc_monday, monday, PID_ABC_MONDAY_HOURS);
> +ABC_ATTRIBUTE(prop_attr_abc_tuesday, tuesday, PID_ABC_TUESDAY_HOURS);
> +ABC_ATTRIBUTE(prop_attr_abc_wednesday, wednesday, PID_ABC_WEDNESDAY_HOURS);
> +ABC_ATTRIBUTE(prop_attr_abc_thursday, thursday, PID_ABC_THURSDAY_HOURS);
> +ABC_ATTRIBUTE(prop_attr_abc_friday, friday, PID_ABC_FRIDAY_HOURS);
> +ABC_ATTRIBUTE(prop_attr_abc_saturday, saturday, PID_ABC_SATURDAY_HOURS);
> +struct attribute *wilco_ec_adv_batt_charging_attrs[] = {
> +       &prop_attr_abc.kobj_attr.attr,
> +       &prop_attr_abc_sunday.kobj_attr.attr,
> +       &prop_attr_abc_monday.kobj_attr.attr,
> +       &prop_attr_abc_tuesday.kobj_attr.attr,
> +       &prop_attr_abc_wednesday.kobj_attr.attr,
> +       &prop_attr_abc_thursday.kobj_attr.attr,
> +       &prop_attr_abc_friday.kobj_attr.attr,
> +       &prop_attr_abc_saturday.kobj_attr.attr,
> +       NULL
> +};
> +ATTRIBUTE_GROUPS(wilco_ec_adv_batt_charging);
> +struct kobject *adv_batt_charging_dir_kobj;
> +
>  /**
>   * wilco_ec_sysfs_init() - Initialize the sysfs directories and attributes
>   * @dev: The device representing the EC
> @@ -112,9 +174,46 @@ int wilco_ec_sysfs_init(struct wilco_ec_device *ec)
>         if (ret)
>                 goto rm_properties_dir;
>
> +       /* add directory for adv batt charging into the properties directory */
> +       adv_batt_charging_dir_kobj = kobject_create_and_add(
> +                                       "advanced_battery_charging",
> +                                       prop_dir_kobj);
> +       if (!adv_batt_charging_dir_kobj) {
> +               ret = -ENOMEM;
> +               goto rm_properties_attrs;
> +       }
> +
> +       /* add the adv batt charging attributes into the abc directory */
> +       ret = sysfs_create_groups(adv_batt_charging_dir_kobj,
> +                                 wilco_ec_adv_batt_charging_groups);
> +       if (ret)
> +               goto rm_abc_dir;
> +
> +       /* add the directory for peakshift into the properties directory */
> +       peakshift_dir_kobj = kobject_create_and_add("peakshift", prop_dir_kobj);
> +       if (!peakshift_dir_kobj) {
> +               ret = -ENOMEM;
> +               goto rm_abc_attrs;
> +       }
> +
> +       /* add the peakshift attributes into the peakshift directory */
> +       ret = sysfs_create_groups(peakshift_dir_kobj,
> +                                 wilco_ec_peakshift_groups);
> +       if (ret)
> +               goto rm_peakshift_dir;
> +
>         return 0;
>
>  /* Go upwards through the directory structure, cleaning up */
> +rm_peakshift_dir:
> +       kobject_put(peakshift_dir_kobj);
> +rm_abc_attrs:
> +       sysfs_remove_groups(adv_batt_charging_dir_kobj,
> +                           wilco_ec_adv_batt_charging_groups);
> +rm_abc_dir:
> +       kobject_put(adv_batt_charging_dir_kobj);
> +rm_properties_attrs:
> +       sysfs_remove_groups(prop_dir_kobj, wilco_ec_property_groups);
>  rm_properties_dir:
>         kobject_put(prop_dir_kobj);
>  rm_toplevel_attrs:
> @@ -128,6 +227,11 @@ void wilco_ec_sysfs_remove(struct wilco_ec_device *ec)
>         struct device *dev = ec->dev;
>
>         /* go upwards through the directory structure */
> +       sysfs_remove_groups(peakshift_dir_kobj, wilco_ec_peakshift_groups);
> +       kobject_put(peakshift_dir_kobj);
> +       sysfs_remove_groups(adv_batt_charging_dir_kobj,
> +                           wilco_ec_adv_batt_charging_groups);
> +       kobject_put(adv_batt_charging_dir_kobj);
>         sysfs_remove_groups(prop_dir_kobj, wilco_ec_property_groups);
>         kobject_put(prop_dir_kobj);
>         sysfs_remove_groups(&dev->kobj, wilco_ec_toplevel_groups);
> --
> 2.20.1.321.g9e740568ce-goog
>

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ