/* * Copyright (C) 2012-2014 Pengutronix, Jan Luebbe * Copyright (C) 2013-2014 Pengutronix, Sascha Hauer * Copyright (C) 2015 Pengutronix, Marc Kleine-Budde * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include "state.h" /** * state_set_dirty - Helper function to set the state to dirty. Only used for * state variables callbacks * @param p * @param priv * @return */ static int state_set_dirty(struct param_d *p, void *priv) { struct state_variable *sv = priv; sv->state->dirty = 1; return 0; } static int state_var_compare(struct list_head *a, struct list_head *b) { struct state_variable *va = list_entry(a, struct state_variable, list); struct state_variable *vb = list_entry(b, struct state_variable, list); return va->start < vb->start ? -1 : 1; } void state_add_var(struct state *state, struct state_variable *var) { list_add_sort(&var->list, &state->variables, state_var_compare); } static int state_uint32_export(struct state_variable *var, struct device_node *node, enum state_convert conv) { struct state_uint32 *su32 = to_state_uint32(var); int ret; if (su32->value_default) { ret = of_property_write_u32(node, "default", su32->value_default); if (ret) return ret; } if (conv == STATE_CONVERT_FIXUP) return 0; return of_property_write_u32(node, "value", su32->value); } static int state_uint32_import(struct state_variable *sv, struct device_node *node) { struct state_uint32 *su32 = to_state_uint32(sv); of_property_read_u32(node, "default", &su32->value_default); if (of_property_read_u32(node, "value", &su32->value)) su32->value = su32->value_default; return 0; } static int state_uint8_set(struct param_d *p, void *priv) { struct state_variable *sv = priv; struct state_uint32 *su32 = to_state_uint32(sv); if (su32->value > 255) return -ERANGE; return state_set_dirty(p, sv); } static struct state_variable *state_uint8_create(struct state *state, const char *name, struct device_node *node, const struct variable_type *vtype) { struct state_uint32 *su32; struct param_d *param; su32 = xzalloc(sizeof(*su32)); param = dev_add_param_uint32(&state->dev, name, state_uint8_set, NULL, &su32->value, "%u", &su32->var); if (IS_ERR(param)) { free(su32); return ERR_CAST(param); } su32->var.type = vtype; su32->var.size = sizeof(uint8_t); #ifdef __LITTLE_ENDIAN su32->var.raw = &su32->value; #else su32->var.raw = &su32->value + 3; #endif su32->var.state = state; return &su32->var; } static struct state_variable *state_uint32_create(struct state *state, const char *name, struct device_node *node, const struct variable_type *vtype) { struct state_uint32 *su32; struct param_d *param; su32 = xzalloc(sizeof(*su32)); param = dev_add_param_uint32(&state->dev, name, state_set_dirty, NULL, &su32->value, "%u", &su32->var); if (IS_ERR(param)) { free(su32); return ERR_CAST(param); } su32->var.type = vtype; su32->var.size = sizeof(uint32_t); su32->var.raw = &su32->value; su32->var.state = state; return &su32->var; } static int state_enum32_export(struct state_variable *var, struct device_node *node, enum state_convert conv) { struct state_enum32 *enum32 = to_state_enum32(var); int ret, i, len; char *prop, *str; if (enum32->value_default) { ret = of_property_write_u32(node, "default", enum32->value_default); if (ret) return ret; } len = 0; for (i = 0; i < enum32->num_names; i++) len += strlen(enum32->names[i]) + 1; prop = xzalloc(len); str = prop; for (i = 0; i < enum32->num_names; i++) str += sprintf(str, "%s", enum32->names[i]) + 1; ret = of_set_property(node, "names", prop, len, 1); free(prop); if (conv == STATE_CONVERT_FIXUP) return 0; ret = of_property_write_u32(node, "value", enum32->value); if (ret) return ret; return ret; } static int state_enum32_import(struct state_variable *sv, struct device_node *node) { struct state_enum32 *enum32 = to_state_enum32(sv); int len; const __be32 *value, *value_default; value = of_get_property(node, "value", &len); if (value && len != sizeof(uint32_t)) return -EINVAL; value_default = of_get_property(node, "default", &len); if (value_default && len != sizeof(uint32_t)) return -EINVAL; if (value_default) enum32->value_default = be32_to_cpu(*value_default); if (value) enum32->value = be32_to_cpu(*value); else enum32->value = enum32->value_default; return 0; } static struct state_variable *state_enum32_create(struct state *state, const char *name, struct device_node *node, const struct variable_type *vtype) { struct state_enum32 *enum32; struct param_d *param; int ret, i, num_names; enum32 = xzalloc(sizeof(*enum32)); num_names = of_property_count_strings(node, "names"); if (num_names < 0) { dev_err(&state->dev, "enum32 node without \"names\" property\n"); return ERR_PTR(-EINVAL); } enum32->names = xzalloc(sizeof(char *) * num_names); enum32->num_names = num_names; enum32->var.type = vtype; enum32->var.size = sizeof(uint32_t); enum32->var.raw = &enum32->value; enum32->var.state = state; for (i = 0; i < num_names; i++) { const char *name; ret = of_property_read_string_index(node, "names", i, &name); if (ret) goto out; enum32->names[i] = xstrdup(name); } param = dev_add_param_enum(&state->dev, name, state_set_dirty, NULL, &enum32->value, enum32->names, num_names, &enum32->var); if (IS_ERR(param)) { ret = PTR_ERR(param); goto out; } return &enum32->var; out: for (i--; i >= 0; i--) free((char *)enum32->names[i]); free(enum32->names); free(enum32); return ERR_PTR(ret); } static int state_mac_export(struct state_variable *var, struct device_node *node, enum state_convert conv) { struct state_mac *mac = to_state_mac(var); int ret; if (!is_zero_ether_addr(mac->value_default)) { ret = of_property_write_u8_array(node, "default", mac->value_default, ARRAY_SIZE(mac-> value_default)); if (ret) return ret; } if (conv == STATE_CONVERT_FIXUP) return 0; return of_property_write_u8_array(node, "value", mac->value, ARRAY_SIZE(mac->value)); } static int state_mac_import(struct state_variable *sv, struct device_node *node) { struct state_mac *mac = to_state_mac(sv); of_property_read_u8_array(node, "default", mac->value_default, ARRAY_SIZE(mac->value_default)); if (of_property_read_u8_array(node, "value", mac->value, ARRAY_SIZE(mac->value))) memcpy(mac->value, mac->value_default, ARRAY_SIZE(mac->value)); return 0; } static struct state_variable *state_mac_create(struct state *state, const char *name, struct device_node *node, const struct variable_type *vtype) { struct state_mac *mac; struct param_d *param; int ret; mac = xzalloc(sizeof(*mac)); mac->var.type = vtype; mac->var.size = ARRAY_SIZE(mac->value); mac->var.raw = mac->value; mac->var.state = state; param = dev_add_param_mac(&state->dev, name, state_set_dirty, NULL, mac->value, &mac->var); if (IS_ERR(param)) { ret = PTR_ERR(param); goto out; } return &mac->var; out: free(mac); return ERR_PTR(ret); } static int state_string_export(struct state_variable *var, struct device_node *node, enum state_convert conv) { struct state_string *string = to_state_string(var); int ret = 0; if (string->value_default) { ret = of_property_write_string(node, "default", string->value_default); if (ret) return ret; } if (conv == STATE_CONVERT_FIXUP) return 0; if (string->value) ret = of_property_write_string(node, "value", string->value); return ret; } static int state_string_import(struct state_variable *sv, struct device_node *node) { struct state_string *string = to_state_string(sv); const char *value = NULL; size_t len; int ret; of_property_read_string(node, "default", &string->value_default); if (string->value_default) { len = strlen(string->value_default); if (len > string->var.size) return -EILSEQ; } ret = of_property_read_string(node, "value", &value); if (ret) value = string->value_default; if (value) return state_string_copy_to_raw(string, value); return 0; } static int state_string_set(struct param_d *p, void *priv) { struct state_variable *sv = priv; struct state_string *string = to_state_string(sv); int ret; ret = state_string_copy_to_raw(string, string->value); if (ret) return ret; return state_set_dirty(p, sv); } static int state_string_get(struct param_d *p, void *priv) { struct state_variable *sv = priv; struct state_string *string = to_state_string(sv); free(string->value); if (string->raw[0]) string->value = xstrndup(string->raw, string->var.size); else string->value = xstrdup(""); return 0; } static struct state_variable *state_string_create(struct state *state, const char *name, struct device_node *node, const struct variable_type *vtype) { struct state_string *string; uint32_t start_size[2]; struct param_d *param; int ret; ret = of_property_read_u32_array(node, "reg", start_size, ARRAY_SIZE(start_size)); if (ret) { dev_err(&state->dev, "%s: reg property not found\n", name); return ERR_PTR(ret); } /* limit to arbitrary len of 4k */ if (start_size[1] > 4096) return ERR_PTR(-EILSEQ); string = xzalloc(sizeof(*string) + start_size[1]); string->var.type = vtype; string->var.size = start_size[1]; string->var.raw = &string->raw; string->var.state = state; param = dev_add_param_string(&state->dev, name, state_string_set, state_string_get, &string->value, &string->var); if (IS_ERR(param)) { ret = PTR_ERR(param); goto out; } return &string->var; out: free(string); return ERR_PTR(ret); } static struct variable_type types[] = { { .type_name = "uint8", .type = STATE_VARIABLE_TYPE_UINT8, .export = state_uint32_export, .import = state_uint32_import, .create = state_uint8_create, }, { .type_name = "uint32", .type = STATE_VARIABLE_TYPE_UINT32, .export = state_uint32_export, .import = state_uint32_import, .create = state_uint32_create, }, { .type_name = "enum32", .type = STATE_VARIABLE_TYPE_ENUM32, .export = state_enum32_export, .import = state_enum32_import, .create = state_enum32_create, }, { .type_name = "mac", .type = STATE_VARIABLE_TYPE_MAC, .export = state_mac_export, .import = state_mac_import, .create = state_mac_create, }, { .type_name = "string", .type = STATE_VARIABLE_TYPE_STRING, .export = state_string_export, .import = state_string_import, .create = state_string_create, } }; struct variable_type *state_find_type_by_name(const char *name) { int i; for (i = 0; i < ARRAY_SIZE(types); i++) { if (!strcmp(name, types[i].type_name)) { return &types[i]; } } return NULL; } struct state_variable *state_find_var(struct state *state, const char *name) { struct state_variable *sv; list_for_each_entry(sv, &state->variables, list) { if (!strcmp(sv->name, name)) return sv; } return ERR_PTR(-ENOENT); }