LCOV - code coverage report
Current view: top level - drivers/power/supply - power_supply_sysfs.c (source / functions) Hit Total Coverage
Test: gcov_data_raspi2_real_modules_combined.info Lines: 5 116 4.3 %
Date: 2020-09-30 20:25:40 Functions: 1 7 14.3 %
Branches: 2 77 2.6 %

           Branch data     Line data    Source code
       1                 :            : // SPDX-License-Identifier: GPL-2.0-only
       2                 :            : /*
       3                 :            :  *  Sysfs interface for the universal power supply monitor class
       4                 :            :  *
       5                 :            :  *  Copyright © 2007  David Woodhouse <dwmw2@infradead.org>
       6                 :            :  *  Copyright © 2007  Anton Vorontsov <cbou@mail.ru>
       7                 :            :  *  Copyright © 2004  Szabolcs Gyurko
       8                 :            :  *  Copyright © 2003  Ian Molton <spyro@f2s.com>
       9                 :            :  *
      10                 :            :  *  Modified: 2004, Oct     Szabolcs Gyurko
      11                 :            :  */
      12                 :            : 
      13                 :            : #include <linux/ctype.h>
      14                 :            : #include <linux/device.h>
      15                 :            : #include <linux/power_supply.h>
      16                 :            : #include <linux/slab.h>
      17                 :            : #include <linux/stat.h>
      18                 :            : 
      19                 :            : #include "power_supply.h"
      20                 :            : 
      21                 :            : /*
      22                 :            :  * This is because the name "current" breaks the device attr macro.
      23                 :            :  * The "current" word resolves to "(get_current())" so instead of
      24                 :            :  * "current" "(get_current())" appears in the sysfs.
      25                 :            :  *
      26                 :            :  * The source of this definition is the device.h which calls __ATTR
      27                 :            :  * macro in sysfs.h which calls the __stringify macro.
      28                 :            :  *
      29                 :            :  * Only modification that the name is not tried to be resolved
      30                 :            :  * (as a macro let's say).
      31                 :            :  */
      32                 :            : 
      33                 :            : #define POWER_SUPPLY_ATTR(_name)                                        \
      34                 :            : {                                                                       \
      35                 :            :         .attr = { .name = #_name },                                     \
      36                 :            :         .show = power_supply_show_property,                             \
      37                 :            :         .store = power_supply_store_property,                           \
      38                 :            : }
      39                 :            : 
      40                 :            : static struct device_attribute power_supply_attrs[];
      41                 :            : 
      42                 :            : static const char * const power_supply_type_text[] = {
      43                 :            :         "Unknown", "Battery", "UPS", "Mains", "USB",
      44                 :            :         "USB_DCP", "USB_CDP", "USB_ACA", "USB_C",
      45                 :            :         "USB_PD", "USB_PD_DRP", "BrickID"
      46                 :            : };
      47                 :            : 
      48                 :            : static const char * const power_supply_usb_type_text[] = {
      49                 :            :         "Unknown", "SDP", "DCP", "CDP", "ACA", "C",
      50                 :            :         "PD", "PD_DRP", "PD_PPS", "BrickID"
      51                 :            : };
      52                 :            : 
      53                 :            : static const char * const power_supply_status_text[] = {
      54                 :            :         "Unknown", "Charging", "Discharging", "Not charging", "Full"
      55                 :            : };
      56                 :            : 
      57                 :            : static const char * const power_supply_charge_type_text[] = {
      58                 :            :         "Unknown", "N/A", "Trickle", "Fast", "Standard", "Adaptive", "Custom"
      59                 :            : };
      60                 :            : 
      61                 :            : static const char * const power_supply_health_text[] = {
      62                 :            :         "Unknown", "Good", "Overheat", "Dead", "Over voltage",
      63                 :            :         "Unspecified failure", "Cold", "Watchdog timer expire",
      64                 :            :         "Safety timer expire", "Over current"
      65                 :            : };
      66                 :            : 
      67                 :            : static const char * const power_supply_technology_text[] = {
      68                 :            :         "Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd",
      69                 :            :         "LiMn"
      70                 :            : };
      71                 :            : 
      72                 :            : static const char * const power_supply_capacity_level_text[] = {
      73                 :            :         "Unknown", "Critical", "Low", "Normal", "High", "Full"
      74                 :            : };
      75                 :            : 
      76                 :            : static const char * const power_supply_scope_text[] = {
      77                 :            :         "Unknown", "System", "Device"
      78                 :            : };
      79                 :            : 
      80                 :          0 : static ssize_t power_supply_show_usb_type(struct device *dev,
      81                 :            :                                           enum power_supply_usb_type *usb_types,
      82                 :            :                                           ssize_t num_usb_types,
      83                 :            :                                           union power_supply_propval *value,
      84                 :            :                                           char *buf)
      85                 :            : {
      86                 :            :         enum power_supply_usb_type usb_type;
      87                 :            :         ssize_t count = 0;
      88                 :            :         bool match = false;
      89                 :            :         int i;
      90                 :            : 
      91         [ #  # ]:          0 :         for (i = 0; i < num_usb_types; ++i) {
      92                 :          0 :                 usb_type = usb_types[i];
      93                 :            : 
      94         [ #  # ]:          0 :                 if (value->intval == usb_type) {
      95                 :          0 :                         count += sprintf(buf + count, "[%s] ",
      96                 :            :                                          power_supply_usb_type_text[usb_type]);
      97                 :            :                         match = true;
      98                 :            :                 } else {
      99                 :          0 :                         count += sprintf(buf + count, "%s ",
     100                 :            :                                          power_supply_usb_type_text[usb_type]);
     101                 :            :                 }
     102                 :            :         }
     103                 :            : 
     104         [ #  # ]:          0 :         if (!match) {
     105                 :          0 :                 dev_warn(dev, "driver reporting unsupported connected type\n");
     106                 :          0 :                 return -EINVAL;
     107                 :            :         }
     108                 :            : 
     109         [ #  # ]:          0 :         if (count)
     110                 :          0 :                 buf[count - 1] = '\n';
     111                 :            : 
     112                 :          0 :         return count;
     113                 :            : }
     114                 :            : 
     115                 :          0 : static ssize_t power_supply_show_property(struct device *dev,
     116                 :            :                                           struct device_attribute *attr,
     117                 :            :                                           char *buf) {
     118                 :            :         ssize_t ret;
     119                 :            :         struct power_supply *psy = dev_get_drvdata(dev);
     120                 :          0 :         enum power_supply_property psp = attr - power_supply_attrs;
     121                 :            :         union power_supply_propval value;
     122                 :            : 
     123         [ #  # ]:          0 :         if (psp == POWER_SUPPLY_PROP_TYPE) {
     124                 :          0 :                 value.intval = psy->desc->type;
     125                 :            :         } else {
     126                 :          0 :                 ret = power_supply_get_property(psy, psp, &value);
     127                 :            : 
     128         [ #  # ]:          0 :                 if (ret < 0) {
     129         [ #  # ]:          0 :                         if (ret == -ENODATA)
     130                 :            :                                 dev_dbg(dev, "driver has no data for `%s' property\n",
     131                 :            :                                         attr->attr.name);
     132         [ #  # ]:          0 :                         else if (ret != -ENODEV && ret != -EAGAIN)
     133         [ #  # ]:          0 :                                 dev_err_ratelimited(dev,
     134                 :            :                                         "driver failed to report `%s' property: %zd\n",
     135                 :            :                                         attr->attr.name, ret);
     136                 :          0 :                         return ret;
     137                 :            :                 }
     138                 :            :         }
     139                 :            : 
     140   [ #  #  #  #  :          0 :         switch (psp) {
          #  #  #  #  #  
                      # ]
     141                 :            :         case POWER_SUPPLY_PROP_STATUS:
     142                 :          0 :                 ret = sprintf(buf, "%s\n",
     143                 :          0 :                               power_supply_status_text[value.intval]);
     144                 :          0 :                 break;
     145                 :            :         case POWER_SUPPLY_PROP_CHARGE_TYPE:
     146                 :          0 :                 ret = sprintf(buf, "%s\n",
     147                 :          0 :                               power_supply_charge_type_text[value.intval]);
     148                 :          0 :                 break;
     149                 :            :         case POWER_SUPPLY_PROP_HEALTH:
     150                 :          0 :                 ret = sprintf(buf, "%s\n",
     151                 :          0 :                               power_supply_health_text[value.intval]);
     152                 :          0 :                 break;
     153                 :            :         case POWER_SUPPLY_PROP_TECHNOLOGY:
     154                 :          0 :                 ret = sprintf(buf, "%s\n",
     155                 :          0 :                               power_supply_technology_text[value.intval]);
     156                 :          0 :                 break;
     157                 :            :         case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
     158                 :          0 :                 ret = sprintf(buf, "%s\n",
     159                 :          0 :                               power_supply_capacity_level_text[value.intval]);
     160                 :          0 :                 break;
     161                 :            :         case POWER_SUPPLY_PROP_TYPE:
     162                 :          0 :                 ret = sprintf(buf, "%s\n",
     163                 :          0 :                               power_supply_type_text[value.intval]);
     164                 :          0 :                 break;
     165                 :            :         case POWER_SUPPLY_PROP_USB_TYPE:
     166                 :          0 :                 ret = power_supply_show_usb_type(dev, psy->desc->usb_types,
     167                 :          0 :                                                  psy->desc->num_usb_types,
     168                 :            :                                                  &value, buf);
     169                 :          0 :                 break;
     170                 :            :         case POWER_SUPPLY_PROP_SCOPE:
     171                 :          0 :                 ret = sprintf(buf, "%s\n",
     172                 :          0 :                               power_supply_scope_text[value.intval]);
     173                 :          0 :                 break;
     174                 :            :         case POWER_SUPPLY_PROP_MODEL_NAME ... POWER_SUPPLY_PROP_SERIAL_NUMBER:
     175                 :          0 :                 ret = sprintf(buf, "%s\n", value.strval);
     176                 :          0 :                 break;
     177                 :            :         default:
     178                 :          0 :                 ret = sprintf(buf, "%d\n", value.intval);
     179                 :            :         }
     180                 :            : 
     181                 :          0 :         return ret;
     182                 :            : }
     183                 :            : 
     184                 :          0 : static ssize_t power_supply_store_property(struct device *dev,
     185                 :            :                                            struct device_attribute *attr,
     186                 :            :                                            const char *buf, size_t count) {
     187                 :            :         ssize_t ret;
     188                 :            :         struct power_supply *psy = dev_get_drvdata(dev);
     189                 :          0 :         enum power_supply_property psp = attr - power_supply_attrs;
     190                 :            :         union power_supply_propval value;
     191                 :            : 
     192   [ #  #  #  #  :          0 :         switch (psp) {
                #  #  # ]
     193                 :            :         case POWER_SUPPLY_PROP_STATUS:
     194                 :          0 :                 ret = sysfs_match_string(power_supply_status_text, buf);
     195                 :          0 :                 break;
     196                 :            :         case POWER_SUPPLY_PROP_CHARGE_TYPE:
     197                 :          0 :                 ret = sysfs_match_string(power_supply_charge_type_text, buf);
     198                 :          0 :                 break;
     199                 :            :         case POWER_SUPPLY_PROP_HEALTH:
     200                 :          0 :                 ret = sysfs_match_string(power_supply_health_text, buf);
     201                 :          0 :                 break;
     202                 :            :         case POWER_SUPPLY_PROP_TECHNOLOGY:
     203                 :          0 :                 ret = sysfs_match_string(power_supply_technology_text, buf);
     204                 :          0 :                 break;
     205                 :            :         case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
     206                 :          0 :                 ret = sysfs_match_string(power_supply_capacity_level_text, buf);
     207                 :          0 :                 break;
     208                 :            :         case POWER_SUPPLY_PROP_SCOPE:
     209                 :          0 :                 ret = sysfs_match_string(power_supply_scope_text, buf);
     210                 :          0 :                 break;
     211                 :            :         default:
     212                 :            :                 ret = -EINVAL;
     213                 :            :         }
     214                 :            : 
     215                 :            :         /*
     216                 :            :          * If no match was found, then check to see if it is an integer.
     217                 :            :          * Integer values are valid for enums in addition to the text value.
     218                 :            :          */
     219         [ #  # ]:          0 :         if (ret < 0) {
     220                 :            :                 long long_val;
     221                 :            : 
     222                 :            :                 ret = kstrtol(buf, 10, &long_val);
     223         [ #  # ]:          0 :                 if (ret < 0)
     224                 :          0 :                         return ret;
     225                 :            : 
     226                 :          0 :                 ret = long_val;
     227                 :            :         }
     228                 :            : 
     229                 :          0 :         value.intval = ret;
     230                 :            : 
     231                 :          0 :         ret = power_supply_set_property(psy, psp, &value);
     232         [ #  # ]:          0 :         if (ret < 0)
     233                 :            :                 return ret;
     234                 :            : 
     235                 :          0 :         return count;
     236                 :            : }
     237                 :            : 
     238                 :            : /* Must be in the same order as POWER_SUPPLY_PROP_* */
     239                 :            : static struct device_attribute power_supply_attrs[] = {
     240                 :            :         /* Properties of type `int' */
     241                 :            :         POWER_SUPPLY_ATTR(status),
     242                 :            :         POWER_SUPPLY_ATTR(charge_type),
     243                 :            :         POWER_SUPPLY_ATTR(health),
     244                 :            :         POWER_SUPPLY_ATTR(present),
     245                 :            :         POWER_SUPPLY_ATTR(online),
     246                 :            :         POWER_SUPPLY_ATTR(authentic),
     247                 :            :         POWER_SUPPLY_ATTR(technology),
     248                 :            :         POWER_SUPPLY_ATTR(cycle_count),
     249                 :            :         POWER_SUPPLY_ATTR(voltage_max),
     250                 :            :         POWER_SUPPLY_ATTR(voltage_min),
     251                 :            :         POWER_SUPPLY_ATTR(voltage_max_design),
     252                 :            :         POWER_SUPPLY_ATTR(voltage_min_design),
     253                 :            :         POWER_SUPPLY_ATTR(voltage_now),
     254                 :            :         POWER_SUPPLY_ATTR(voltage_avg),
     255                 :            :         POWER_SUPPLY_ATTR(voltage_ocv),
     256                 :            :         POWER_SUPPLY_ATTR(voltage_boot),
     257                 :            :         POWER_SUPPLY_ATTR(current_max),
     258                 :            :         POWER_SUPPLY_ATTR(current_now),
     259                 :            :         POWER_SUPPLY_ATTR(current_avg),
     260                 :            :         POWER_SUPPLY_ATTR(current_boot),
     261                 :            :         POWER_SUPPLY_ATTR(power_now),
     262                 :            :         POWER_SUPPLY_ATTR(power_avg),
     263                 :            :         POWER_SUPPLY_ATTR(charge_full_design),
     264                 :            :         POWER_SUPPLY_ATTR(charge_empty_design),
     265                 :            :         POWER_SUPPLY_ATTR(charge_full),
     266                 :            :         POWER_SUPPLY_ATTR(charge_empty),
     267                 :            :         POWER_SUPPLY_ATTR(charge_now),
     268                 :            :         POWER_SUPPLY_ATTR(charge_avg),
     269                 :            :         POWER_SUPPLY_ATTR(charge_counter),
     270                 :            :         POWER_SUPPLY_ATTR(constant_charge_current),
     271                 :            :         POWER_SUPPLY_ATTR(constant_charge_current_max),
     272                 :            :         POWER_SUPPLY_ATTR(constant_charge_voltage),
     273                 :            :         POWER_SUPPLY_ATTR(constant_charge_voltage_max),
     274                 :            :         POWER_SUPPLY_ATTR(charge_control_limit),
     275                 :            :         POWER_SUPPLY_ATTR(charge_control_limit_max),
     276                 :            :         POWER_SUPPLY_ATTR(charge_control_start_threshold),
     277                 :            :         POWER_SUPPLY_ATTR(charge_control_end_threshold),
     278                 :            :         POWER_SUPPLY_ATTR(input_current_limit),
     279                 :            :         POWER_SUPPLY_ATTR(input_voltage_limit),
     280                 :            :         POWER_SUPPLY_ATTR(input_power_limit),
     281                 :            :         POWER_SUPPLY_ATTR(energy_full_design),
     282                 :            :         POWER_SUPPLY_ATTR(energy_empty_design),
     283                 :            :         POWER_SUPPLY_ATTR(energy_full),
     284                 :            :         POWER_SUPPLY_ATTR(energy_empty),
     285                 :            :         POWER_SUPPLY_ATTR(energy_now),
     286                 :            :         POWER_SUPPLY_ATTR(energy_avg),
     287                 :            :         POWER_SUPPLY_ATTR(capacity),
     288                 :            :         POWER_SUPPLY_ATTR(capacity_alert_min),
     289                 :            :         POWER_SUPPLY_ATTR(capacity_alert_max),
     290                 :            :         POWER_SUPPLY_ATTR(capacity_level),
     291                 :            :         POWER_SUPPLY_ATTR(temp),
     292                 :            :         POWER_SUPPLY_ATTR(temp_max),
     293                 :            :         POWER_SUPPLY_ATTR(temp_min),
     294                 :            :         POWER_SUPPLY_ATTR(temp_alert_min),
     295                 :            :         POWER_SUPPLY_ATTR(temp_alert_max),
     296                 :            :         POWER_SUPPLY_ATTR(temp_ambient),
     297                 :            :         POWER_SUPPLY_ATTR(temp_ambient_alert_min),
     298                 :            :         POWER_SUPPLY_ATTR(temp_ambient_alert_max),
     299                 :            :         POWER_SUPPLY_ATTR(time_to_empty_now),
     300                 :            :         POWER_SUPPLY_ATTR(time_to_empty_avg),
     301                 :            :         POWER_SUPPLY_ATTR(time_to_full_now),
     302                 :            :         POWER_SUPPLY_ATTR(time_to_full_avg),
     303                 :            :         POWER_SUPPLY_ATTR(type),
     304                 :            :         POWER_SUPPLY_ATTR(usb_type),
     305                 :            :         POWER_SUPPLY_ATTR(scope),
     306                 :            :         POWER_SUPPLY_ATTR(precharge_current),
     307                 :            :         POWER_SUPPLY_ATTR(charge_term_current),
     308                 :            :         POWER_SUPPLY_ATTR(calibrate),
     309                 :            :         /* Properties of type `const char *' */
     310                 :            :         POWER_SUPPLY_ATTR(model_name),
     311                 :            :         POWER_SUPPLY_ATTR(manufacturer),
     312                 :            :         POWER_SUPPLY_ATTR(serial_number),
     313                 :            : };
     314                 :            : 
     315                 :            : static struct attribute *
     316                 :            : __power_supply_attrs[ARRAY_SIZE(power_supply_attrs) + 1];
     317                 :            : 
     318                 :          0 : static umode_t power_supply_attr_is_visible(struct kobject *kobj,
     319                 :            :                                            struct attribute *attr,
     320                 :            :                                            int attrno)
     321                 :            : {
     322                 :            :         struct device *dev = container_of(kobj, struct device, kobj);
     323                 :            :         struct power_supply *psy = dev_get_drvdata(dev);
     324                 :            :         umode_t mode = S_IRUSR | S_IRGRP | S_IROTH;
     325                 :            :         int i;
     326                 :            : 
     327         [ #  # ]:          0 :         if (attrno == POWER_SUPPLY_PROP_TYPE)
     328                 :            :                 return mode;
     329                 :            : 
     330         [ #  # ]:          0 :         for (i = 0; i < psy->desc->num_properties; i++) {
     331                 :          0 :                 int property = psy->desc->properties[i];
     332                 :            : 
     333         [ #  # ]:          0 :                 if (property == attrno) {
     334   [ #  #  #  # ]:          0 :                         if (psy->desc->property_is_writeable &&
     335                 :          0 :                             psy->desc->property_is_writeable(psy, property) > 0)
     336                 :            :                                 mode |= S_IWUSR;
     337                 :            : 
     338                 :          0 :                         return mode;
     339                 :            :                 }
     340                 :            :         }
     341                 :            : 
     342                 :            :         return 0;
     343                 :            : }
     344                 :            : 
     345                 :            : static struct attribute_group power_supply_attr_group = {
     346                 :            :         .attrs = __power_supply_attrs,
     347                 :            :         .is_visible = power_supply_attr_is_visible,
     348                 :            : };
     349                 :            : 
     350                 :            : static const struct attribute_group *power_supply_attr_groups[] = {
     351                 :            :         &power_supply_attr_group,
     352                 :            :         NULL,
     353                 :            : };
     354                 :            : 
     355                 :        207 : void power_supply_init_attrs(struct device_type *dev_type)
     356                 :            : {
     357                 :            :         int i;
     358                 :            : 
     359                 :        207 :         dev_type->groups = power_supply_attr_groups;
     360                 :            : 
     361         [ +  + ]:      14904 :         for (i = 0; i < ARRAY_SIZE(power_supply_attrs); i++)
     362                 :      14697 :                 __power_supply_attrs[i] = &power_supply_attrs[i].attr;
     363                 :        207 : }
     364                 :            : 
     365                 :          0 : static char *kstruprdup(const char *str, gfp_t gfp)
     366                 :            : {
     367                 :            :         char *ret, *ustr;
     368                 :            : 
     369                 :          0 :         ustr = ret = kmalloc(strlen(str) + 1, gfp);
     370                 :            : 
     371         [ #  # ]:          0 :         if (!ret)
     372                 :            :                 return NULL;
     373                 :            : 
     374         [ #  # ]:          0 :         while (*str)
     375                 :          0 :                 *ustr++ = toupper(*str++);
     376                 :            : 
     377                 :          0 :         *ustr = 0;
     378                 :            : 
     379                 :          0 :         return ret;
     380                 :            : }
     381                 :            : 
     382                 :          0 : int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env)
     383                 :            : {
     384                 :            :         struct power_supply *psy = dev_get_drvdata(dev);
     385                 :            :         int ret = 0, j;
     386                 :            :         char *prop_buf;
     387                 :            :         char *attrname;
     388                 :            : 
     389   [ #  #  #  # ]:          0 :         if (!psy || !psy->desc) {
     390                 :            :                 dev_dbg(dev, "No power supply yet\n");
     391                 :            :                 return ret;
     392                 :            :         }
     393                 :            : 
     394                 :          0 :         ret = add_uevent_var(env, "POWER_SUPPLY_NAME=%s", psy->desc->name);
     395         [ #  # ]:          0 :         if (ret)
     396                 :            :                 return ret;
     397                 :            : 
     398                 :          0 :         prop_buf = (char *)get_zeroed_page(GFP_KERNEL);
     399         [ #  # ]:          0 :         if (!prop_buf)
     400                 :            :                 return -ENOMEM;
     401                 :            : 
     402         [ #  # ]:          0 :         for (j = 0; j < psy->desc->num_properties; j++) {
     403                 :            :                 struct device_attribute *attr;
     404                 :            :                 char *line;
     405                 :            : 
     406                 :          0 :                 attr = &power_supply_attrs[psy->desc->properties[j]];
     407                 :            : 
     408                 :          0 :                 ret = power_supply_show_property(dev, attr, prop_buf);
     409         [ #  # ]:          0 :                 if (ret == -ENODEV || ret == -ENODATA) {
     410                 :            :                         /* When a battery is absent, we expect -ENODEV. Don't abort;
     411                 :            :                            send the uevent with at least the the PRESENT=0 property */
     412                 :            :                         ret = 0;
     413                 :          0 :                         continue;
     414                 :            :                 }
     415                 :            : 
     416         [ #  # ]:          0 :                 if (ret < 0)
     417                 :            :                         goto out;
     418                 :            : 
     419                 :          0 :                 line = strchr(prop_buf, '\n');
     420         [ #  # ]:          0 :                 if (line)
     421                 :          0 :                         *line = 0;
     422                 :            : 
     423                 :          0 :                 attrname = kstruprdup(attr->attr.name, GFP_KERNEL);
     424         [ #  # ]:          0 :                 if (!attrname) {
     425                 :            :                         ret = -ENOMEM;
     426                 :            :                         goto out;
     427                 :            :                 }
     428                 :            : 
     429                 :          0 :                 ret = add_uevent_var(env, "POWER_SUPPLY_%s=%s", attrname, prop_buf);
     430                 :          0 :                 kfree(attrname);
     431         [ #  # ]:          0 :                 if (ret)
     432                 :            :                         goto out;
     433                 :            :         }
     434                 :            : 
     435                 :            : out:
     436                 :          0 :         free_page((unsigned long)prop_buf);
     437                 :            : 
     438                 :          0 :         return ret;
     439                 :            : }

Generated by: LCOV version 1.14