From 3cb2210e9a730bb0ff0e0ed3e90ae77d8624a72a Mon Sep 17 00:00:00 2001 From: huaqin13 Date: Mon, 15 Oct 2018 17:10:29 +0800 Subject: [PATCH] (CR):[Kane]:[factory]utags:add utags related kernel driver code Change-Id: If4e894a67be9cdca347fa1d19c50ed49fc7e17d7 Signed-off-by: huaqin13 --- .../boot/dts/exynos/exynos9610-mot-kane.dtsi | 20 + drivers/misc/utag/Kconfig | 8 + drivers/misc/utag/Makefile | 1 + drivers/misc/utag/utags.c | 1955 +++++++++++++++++ 4 files changed, 1984 insertions(+) create mode 100644 arch/arm64/boot/dts/exynos/exynos9610-mot-kane.dtsi create mode 100644 drivers/misc/utag/Kconfig create mode 100644 drivers/misc/utag/Makefile create mode 100644 drivers/misc/utag/utags.c diff --git a/arch/arm64/boot/dts/exynos/exynos9610-mot-kane.dtsi b/arch/arm64/boot/dts/exynos/exynos9610-mot-kane.dtsi new file mode 100644 index 000000000000..07dc2610b826 --- /dev/null +++ b/arch/arm64/boot/dts/exynos/exynos9610-mot-kane.dtsi @@ -0,0 +1,20 @@ + +/ { + fragment@utag { + target-path = "/"; + __overlay__ { + utags { + compatible = "mmi,utags"; + mmi,main-utags = "/dev/block/platform/13520000.ufs/by-name/utags"; + mmi,backup-utags = "/dev/block/platform/13520000.ufs/by-name/utagsBackup"; + }; + + hw { + compatible = "mmi,utags"; + mmi,dir-name = "hw"; + mmi,main-utags = "/dev/block/platform/13520000.ufs/by-name/hw"; + }; + + }; + }; +}; diff --git a/drivers/misc/utag/Kconfig b/drivers/misc/utag/Kconfig new file mode 100644 index 000000000000..d6942fd67fa5 --- /dev/null +++ b/drivers/misc/utag/Kconfig @@ -0,0 +1,8 @@ +config MOT_UTAG + tristate "Motorola Configuration read/write support " + default n + help + With this option you get read/write access to utag partition via procfs. + Content of the entire partition can be checked, separate utags accessed + and modified. + diff --git a/drivers/misc/utag/Makefile b/drivers/misc/utag/Makefile new file mode 100644 index 000000000000..638187ce5ee6 --- /dev/null +++ b/drivers/misc/utag/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_MOT_UTAG) += utags.o diff --git a/drivers/misc/utag/utags.c b/drivers/misc/utag/utags.c new file mode 100644 index 000000000000..ff4734e8d2c3 --- /dev/null +++ b/drivers/misc/utag/utags.c @@ -0,0 +1,1955 @@ +/* Copyright (c) 2018, Motorola Mobility LLC. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + * + */ +#define pr_fmt(fmt) "utags (%s): " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_UTAG_SIZE 1024 +#define MAX_UTAG_NAME 32 +#define UTAG_DEPTH 5 +#define UTAG_HEAD "__UTAG_HEAD__" +#define UTAG_TAIL "__UTAG_TAIL__" +#define ROUNDUP(a, b) (((a) + ((b)-1)) & ~((b)-1)) +#define TO_SECT_SIZE(n) (((n) + 511) & ~511) +#define DRVNAME "utags" +#define DEFAULT_ROOT "config" +#define HW_ROOT "hw" + +static const struct file_operations utag_fops; +struct ctrl; + +enum utag_flag { + UTAG_FLAG_PROTECTED = 1 << 0, +}; + +#define UTAG_STATUS_LOADED '0' +#define UTAG_STATUS_RELOAD '1' +#define UTAG_STATUS_NOT_READY '2' +#define UTAG_STATUS_FAILED '3' + +struct utag { + char name[MAX_UTAG_NAME]; /* UTAG name and type combined */ + char name_only[MAX_UTAG_NAME]; /* UTAG name with type removed */ + uint32_t size; + uint32_t flags; + uint32_t util; + void *payload; + struct utag *next; + struct utag *prev; +}; + +struct frozen_utag { + char name[MAX_UTAG_NAME]; + uint32_t size; + uint32_t flags; + uint32_t util; + uint8_t payload[]; +}; + +#define UTAG_MIN_TAG_SIZE (sizeof(struct frozen_utag)) + +enum utag_output { + OUT_ASCII = 0, + OUT_RAW, + OUT_TYPE +}; + +static char *files[] = { + "ascii", + "raw", + "type" +}; + +struct proc_node { + struct list_head entry; + char name[MAX_UTAG_NAME]; /* UTAG name string */ + char type[MAX_UTAG_NAME]; /* UTAG type string */ + char file_name[MAX_UTAG_NAME]; + struct proc_dir_entry *file; + struct proc_dir_entry *dir; + uint32_t mode; + struct ctrl *ctrl; +}; + +struct dir_node { + struct list_head entry; + char name[MAX_UTAG_NAME]; + char path[MAX_UTAG_NAME]; + struct proc_dir_entry *dir; + struct proc_dir_entry *parent; + struct ctrl *ctrl; +}; + +struct blkdev { + const char *name; + struct file *filep; + size_t size; +}; + +struct ctrl { + struct blkdev main; + struct blkdev backup; + struct platform_device *pdev; + struct proc_dir_entry *root; + char reload; + size_t rsize; + struct list_head dir_list; + struct list_head node_list; + const char *dir_name; + uint32_t lock; + uint32_t hwtag; + struct mutex access_lock; + struct utag *attrib; + struct utag *features; + struct completion load_comp; + struct completion store_comp; + struct workqueue_struct *load_queue; + struct workqueue_struct *store_queue; + struct work_struct load_work; + struct work_struct store_work; + struct utag *head; +}; + +static int build_utags_directory(struct ctrl *ctrl); +static void clear_utags_directory(struct ctrl *ctrl); + +static int store_utags(struct ctrl *ctrl, struct utag *tags); + +static ssize_t write_utag(struct file *file, const char __user *buffer, + size_t count, loff_t *pos); +static ssize_t new_utag(struct file *file, const char __user *buffer, + size_t count, loff_t *pos); +static ssize_t delete_utag(struct file *file, const char __user *buffer, + size_t count, loff_t *pos); +static int add_utag_tail(struct utag *head, char *utag_name, char *utag_type); + +static int lock_open(struct inode *inode, struct file *file); +static int partition_open(struct inode *inode, struct file *file); + +static const struct file_operations utag_fops = { + .owner = THIS_MODULE, + .open = partition_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = write_utag, +}; + +static const struct file_operations new_fops = { + .read = NULL, + .write = new_utag, +}; + +static const struct file_operations lock_fops = { + .open = lock_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static const struct file_operations delete_fops = { + .read = NULL, + .write = delete_utag, +}; + +/* + * check utag name + * for special symbols + * + */ + +static int no_show_tag(char *name) +{ + if (('/' == name[0]) && (0 == name[1])) { + pr_err("skipping bl root tag"); + return 1; + } + + if (('.' == name[0]) && (0 == name[1])) { + pr_err("skipping . tag"); + return 1; + } + + return 0; +} + +/* + * Read tags head + */ + +static int read_head(struct blkdev *cb, struct utag *htag) +{ + int bytes; + struct frozen_utag buf; + loff_t pos = 0; + + if (!htag) { + pr_err("[%s] null pointer", cb->name); + return -EINVAL; + } + + bytes = kernel_read(cb->filep, (void *) &buf, UTAG_MIN_TAG_SIZE, &pos); + if ((int) UTAG_MIN_TAG_SIZE > bytes) { + pr_err("ERR file (%s) read failed ret %d\n", cb->name, bytes); + return -EIO; + } + + strlcpy(htag->name, buf.name, MAX_UTAG_NAME - 1); + if (strncmp(htag->name, UTAG_HEAD, MAX_UTAG_NAME)) { + pr_err("[%s] invalid or empty utags partition\n", cb->name); + return -EIO; + } + htag->flags = ntohl(buf.flags); + htag->util = ntohl(buf.util); + htag->size = ntohl(buf.size); + pr_debug("utag file (%s) flags %#x util %#x size %#x\n", + cb->name, htag->flags, htag->util, htag->size); + return 0; +} + +/* + * Initialize empty tags + * + */ +static int init_empty(struct ctrl *ctrl) +{ + struct utag *htag; + + pr_err("erasing [%s]\n", ctrl->dir_name); + htag = kzalloc(sizeof(struct utag), GFP_KERNEL); + if (!htag) + return -ENOMEM; + + strlcpy(htag->name, UTAG_HEAD, MAX_UTAG_NAME); + add_utag_tail(htag, UTAG_TAIL, NULL); + ctrl->head = htag; + queue_work(ctrl->store_queue, &ctrl->store_work); + wait_for_completion(&ctrl->store_comp); + kfree(htag); + return 0; +} + +static char *get_dir_name(struct ctrl *ctrl, struct proc_dir_entry *dir) +{ + struct dir_node *c, *dir_node = NULL; + + list_for_each_entry(c, &ctrl->dir_list, entry) { + if (c->dir == dir) { + dir_node = c; + break; + } + } + return dir_node ? dir_node->name : "na"; +} + +/* + * Check util field of head utag for actual data size + * + */ +static size_t data_size(struct blkdev *cb) +{ + size_t bytes; + struct utag htag; + struct ctrl *ctrl = container_of(cb, struct ctrl, main); + + memset(&htag, 0, sizeof(struct utag)); + if (read_head(cb, &htag)) + return 0; + + ctrl->lock = htag.flags & UTAG_FLAG_PROTECTED ? 1 : 0; + bytes = htag.util; + pr_debug("file (%s) saved %zu block %zu flags %#x ctrl %zu\n", + cb->name, bytes, cb->size, htag.flags, ctrl->rsize); + + /* On the first read always load entire partition */ + if (ctrl->hwtag) { + pr_debug("[%s] hwtag sizes\n", ctrl->dir_name); + if (!ctrl->rsize || !bytes || bytes > cb->size) + bytes = cb->size; + } else + bytes = cb->size; + + ctrl->rsize = bytes; + + pr_debug("[%s] reading %zu bytes\n", ctrl->dir_name, bytes); + return bytes; +} + +/* + * Open and store file handle for a utag partition + * + * Not thread safe, call from a safe context only + */ +static int open_utags(struct blkdev *cb) +{ + struct inode *inode = NULL; + + if (!cb->name) + return -EIO; + + if (cb->filep) + return 0; + + cb->filep = filp_open(cb->name, O_RDWR|O_SYNC, 0600); + if (IS_ERR_OR_NULL(cb->filep)) { + int rc = PTR_ERR(cb->filep); + if (rc == -EROFS) { + pr_info("[%s] readonly, open as O_RDONLY\n", cb->name); + cb->filep = filp_open(cb->name, O_RDONLY|O_SYNC, 0600); + if (IS_ERR_OR_NULL(cb->filep)) { + rc = PTR_ERR(cb->filep); + pr_err("opening (%s) errno=%d\n", cb->name, rc); + cb->filep = NULL; + return rc; + } + } else { + pr_err("opening (%s) errno=%d\n", cb->name, rc); + cb->filep = NULL; + return rc; + } + } + + if (cb->filep->f_path.dentry) + inode = cb->filep->f_path.dentry->d_inode; + if (!inode || !S_ISBLK(inode->i_mode)) { + pr_err("(%s) not a block device\n", cb->name); + filp_close(cb->filep, NULL); + cb->filep = NULL; + return -EIO; + } + + cb->size = i_size_read(inode->i_bdev->bd_inode); + pr_debug("[%s] (pid %i) open (%s) success\n", + current->comm, current->pid, cb->name); + return 0; +} + + +/* + * Free the memory associated with a list of tags. + * + */ + +static inline void free_tags(struct utag *tags) +{ + struct utag *next; + + while (tags) { + next = tags->next; + kfree(tags->payload); + kfree(tags); + tags = next; + } +} + +static void walk_tags(struct utag *tags) +{ + struct utag *next; + + while (tags) { + next = tags->next; + pr_debug("utag [%s], payload size %u\n", + tags->name, tags->size); + tags = next; + } +} + +static inline void walk_proc_nodes(struct ctrl *ctrl) +{ + struct proc_node *p; + + list_for_each_entry(p, &ctrl->node_list, entry) { + pr_debug("proc-node [%s:%s:%s]\n", + p->name, p->type, p->file_name); + } +} + +static inline void walk_dir_nodes(struct ctrl *ctrl) +{ + struct dir_node *d; + + list_for_each_entry(d, &ctrl->dir_list, entry) { + pr_debug("dir-node [%s] path [%s]\n", d->name, d->path); + } +} + +/* + * compare names, break check at any null in names + * returns true if match, false otherwise + */ + +static inline bool names_match(const char *s1, const char *s2) +{ + register size_t count = MAX_UTAG_NAME; + register int r, c1, c2; + + pr_debug("cmp (%s) <=> (%s)\n)", s1, s2); + while (count--) { + c1 = *s1++; + c2 = *s2++; + r = c1 - c2; + if (r || !c1 || !c2) + return (r) ? false : true; + } + return true; +} + +/* + * + * Check for name to have single ':' char + * not in the first or last position + * + * returns true if name is OK, false otherwise + */ + +static inline bool validate_name(const char *s1, int count) +{ + register int c1 = *s1, sep = 0; + + if (c1 == ':') + return false; + while (count--) { + if (c1 == ':') + sep++; + if (sep > 1) + return false; + c1 = *s1++; + } + if (c1 == ':') + return false; + return true; +} + +/* + * validate path to have :type as a last element + */ + +static int check_path(char *fullpath, int count) +{ + char *ptr, *bptr; + + ptr = strnchr(fullpath, count, ':'); + bptr = strnchr(fullpath, count, '/'); + if (bptr && ptr && bptr > ptr) { + pr_err("Invalid path %s\n", fullpath); + return -EINVAL; + } + + return 0; +} + + +/* + * Extract hierarchical names from the fullpath + * Return: number of pointers in name array + */ + +static int full_split(char *fullpath, char **name, char **type) +{ + int i; + char *ptr; + + ptr = strnchr(fullpath, MAX_UTAG_NAME, ':'); + if (ptr) { + *ptr++ = 0; + *type = ptr; + pr_debug("type=%s\n", *type); + } + + for (i = 0, ptr = fullpath; ptr; i++) { + if (*ptr == '/') + *ptr++ = 0; + name[i] = ptr; + ptr = strnchr(ptr, MAX_UTAG_NAME, '/'); + pr_debug("name[%d]=%s\n", i, name[i]); + } + + return i; +} + +static int add_utag_tail(struct utag *head, char *utag_name, char *utag_type) +{ + char utag[MAX_UTAG_NAME]; + struct utag *new, *tail; + + scnprintf(utag, MAX_UTAG_NAME, "%s%s%s", utag_name, + utag_type ? ":" : "", utag_type ? utag_type : ""); + + /* find list tail and check for duplicate utag */ + for (tail = head; tail->next; tail = tail->next) { + if (!strncmp(tail->name_only, utag, MAX_UTAG_NAME)) { + pr_debug("utag [%s] already exists\n", utag); + return -EEXIST; + } + } + pr_debug("tail utag [%s]\n", tail->name); + + new = kzalloc(sizeof(struct utag), GFP_KERNEL); + if (!new) + return -ENOMEM; + + strlcpy(new->name, utag, MAX_UTAG_NAME); + new->size = new->flags = new->util = 0; + + if (!tail->prev) { /* tail is in fact the head */ + tail->next = new; + new->prev = tail; + } else { + /* insert new utag before tail */ + new->prev = tail->prev; + new->next = tail; + tail->prev->next = new; + tail->prev = new; + } + walk_tags(head); + + return 0; +} + +/* + * Find first instance of utag by specified name + */ + +static struct utag *find_first_utag(const struct utag *head, const char *name) +{ + struct utag *cur; + + if (!head) + return NULL; + + cur = head->next; /* skip HEAD */ + while (cur) { + /* skip TAIL */ + if (cur->next == NULL) + break; + if (names_match(name, cur->name)) + return cur; + cur = cur->next; + } + return NULL; +} + +/* + * Create, initialize add to the list procfs utag file node + */ + +static int proc_utag_file(char *utag_name, char *utag_type, + enum utag_output mode, struct dir_node *dnode, + const struct file_operations *fops) +{ + struct proc_node *node; + struct ctrl *ctrl = dnode->ctrl; + + if (sizeof(files) < mode) + return -EINVAL; + + node = kzalloc(sizeof(struct proc_node), GFP_KERNEL); + if (node) { + list_add_tail(&node->entry, &ctrl->node_list); + strlcpy(node->file_name, files[mode], MAX_UTAG_NAME); + strlcpy(node->name, utag_name, MAX_UTAG_NAME); + if (utag_type) + strlcpy(node->type, utag_type, MAX_UTAG_NAME); + else + node->type[0] = 0; + node->mode = mode; + node->dir = dnode->dir; + node->ctrl = ctrl; + node->file = proc_create_data(node->file_name, 0, + dnode->dir, fops, node); + + pr_debug("created file [%s/%s]\n", + get_dir_name(ctrl, dnode->dir), node->file_name); + } + return 0; +} + +static struct dir_node *find_dir_node(struct ctrl *ctrl, char *path) +{ + struct dir_node *c, *dir_node = NULL; + + list_for_each_entry(c, &ctrl->dir_list, entry) { + if (!strncmp(c->path, path, MAX_UTAG_NAME)) { + dir_node = c; + break; + } + } + return dir_node; +} + +static int proc_utag_util(struct ctrl *ctrl) +{ + struct proc_dir_entry *dir; + + dir = proc_mkdir("all", ctrl->root); + if (!dir) { + pr_err("failed to create util\n"); + return -EIO; + } + + if (!proc_create_data("new", 0600, dir, &new_fops, ctrl)) { + pr_err("Failed to create utag new entry\n"); + return -EIO; + } + + if (!proc_create_data("lock", 0600, dir, &lock_fops, ctrl)) { + pr_err("Failed to create lock entry\n"); + return -EIO; + } + + if (!proc_create_data(".delete", 0600, dir, &delete_fops, ctrl)) { + pr_err("Failed to create delete entry\n"); + return -EIO; + } + + return 0; +} + +static struct proc_dir_entry *proc_utag_dir(struct ctrl *ctrl, + char *tname, char *path, char *ttype, bool populate, + struct proc_dir_entry *parent) +{ + struct dir_node *dnode; + struct proc_dir_entry *dir = NULL; + + if (!parent) + parent = ctrl->root; + + dnode = find_dir_node(ctrl, path); + if (dnode) { + if (populate) + goto populate_utag_dir; + + pr_debug("procfs dir %s exists; skip\n", tname); + return dnode->dir; + } + + dir = proc_mkdir(tname, parent); + if (!dir) { + pr_err("failed to create dir %s\n", tname); + return ERR_PTR(-ENOMEM); + } + + dnode = kzalloc(sizeof(struct dir_node), GFP_KERNEL); + if (!dnode) { + kfree(dir); + pr_err("failed to create node structure\n"); + return ERR_PTR(-ENOMEM); + } + + dnode->parent = parent; + dnode->ctrl = ctrl; + dnode->dir = dir; + list_add_tail(&dnode->entry, &ctrl->dir_list); + strlcpy(dnode->name, tname, MAX_UTAG_NAME); + strlcpy(dnode->path, path, MAX_UTAG_NAME); + + if (!populate) + return dir; + +populate_utag_dir: + + proc_utag_file(tname, ttype, OUT_ASCII, dnode, &utag_fops); + proc_utag_file(tname, ttype, OUT_RAW, dnode, &utag_fops); + proc_utag_file(tname, ttype, OUT_TYPE, dnode, &utag_fops); + + return dir; +} + +/* + * Convert a block of tags, presumably loaded from seconday storage, into a + * format that can be manipulated. + */ +static struct utag *thaw_tags(size_t block_size, void *buf) +{ + struct utag *head = NULL, *cur = NULL; + uint8_t *ptr = buf; + + while (1) { + struct frozen_utag *frozen; + uint8_t *next_ptr; + char *sep; + + frozen = (struct frozen_utag *)ptr; + + if (!head) { + /* This is allocation of the head */ + cur = kzalloc(sizeof(struct utag), GFP_KERNEL); + if (!cur) + return NULL; + } + + strlcpy(cur->name, frozen->name, MAX_UTAG_NAME - 1); + strlcpy(cur->name_only, frozen->name, MAX_UTAG_NAME-1); + sep = strnchr(cur->name_only, MAX_UTAG_NAME, ':'); + if (sep) + *sep = 0; + cur->flags = ntohl(frozen->flags); + cur->util = ntohl(frozen->util); + cur->size = ntohl(frozen->size); + + if (!head) { + head = cur; + + if (strcmp(head->name, UTAG_HEAD)) { + pr_err("invalid or empty utags partition\n"); + goto err_free; + } + } + + /* moved here to print statistics for tail as well */ + next_ptr = ptr + UTAG_MIN_TAG_SIZE + ROUNDUP(cur->size, 4); + pr_debug("utag [%s] size %zu\n", cur->name, next_ptr - ptr); + + /* check if this is the end */ + if (!strcmp(cur->name, UTAG_TAIL)) { + /* footer payload size should be zero */ + if (0 != cur->size) { + pr_err("invalid utags tail\n"); + goto err_free; + } + + /* all done */ + break; + } + + /* + * Ensure there is enough space in the buffer for both the + * payload and the tag header for the next tag. + */ + if ((next_ptr - (uint8_t *) buf) + UTAG_MIN_TAG_SIZE > + block_size) { + pr_err("invalid tags size\n"); + goto err_free; + } + + if (cur->size != 0) { + cur->payload = kzalloc(cur->size, GFP_KERNEL); + if (!cur->payload) + goto err_free; + memcpy(cur->payload, frozen->payload, cur->size); + } + + /* advance to beginning of next tag */ + ptr = next_ptr; + + /* get ready for the next tag */ + cur->next = kzalloc(sizeof(struct utag), GFP_KERNEL); + /* FIXME if kzalloc fails, kernel will panic in the next line */ + cur->next->prev = cur; + cur = cur->next; + if (!cur) + goto err_free; + } /* while (1) */ + + walk_tags(head); + goto out; + + err_free: + free_tags(head); + head = NULL; + out: + return head; +} + +static void *freeze_tags(size_t block_size, struct utag *tags, + size_t *tags_size) +{ + size_t written, frozen_size = 0; + char *buf = NULL, *ptr; + struct utag *cur = tags; + size_t zeros; + struct frozen_utag frozen = { {0} }; + + /* Make sure the tags start with the HEAD marker. */ + if (!tags || strncmp(tags->name, UTAG_HEAD, MAX_UTAG_NAME)) { + pr_err("invalid utags head\n"); + return NULL; + } + + /* + * Walk the list once to determine the amount of space to allocate + * for the frozen tags. + */ + while (cur) { + pr_debug("utag [%s], payload size %u\n", cur->name, cur->size); + frozen_size += ROUNDUP(cur->size, 4) + UTAG_MIN_TAG_SIZE; + pr_debug("calculated size %zu\n", frozen_size); + if (!strncmp(cur->name, UTAG_TAIL, MAX_UTAG_NAME)) + break; + cur = cur->next; + } + + /* round up frozen_size to eMMC sector size */ + frozen_size = TO_SECT_SIZE(frozen_size); + pr_debug("frozen size aligned to sector size %zu\n", frozen_size); + + /* do some more sanity checking */ + if (!cur || cur->next) { + pr_err("utags corrupted\n"); + return NULL; + } + + if (block_size < frozen_size) { + pr_err("utag size %zu too big\n", frozen_size); + return NULL; + } + + ptr = buf = vmalloc(frozen_size); + if (!buf) + return NULL; + + cur = tags; + /* root utag stores size of entire image in util word */ + cur->util = frozen_size; + while (1) { + written = 0; + memcpy(frozen.name, cur->name, MAX_UTAG_NAME); + frozen.flags = htonl(cur->flags); + frozen.size = htonl(cur->size); + frozen.util = htonl(cur->util); + + memcpy(ptr, &frozen, UTAG_MIN_TAG_SIZE); + ptr += UTAG_MIN_TAG_SIZE; + written += UTAG_MIN_TAG_SIZE; + + if (cur->size) { + memcpy(ptr, cur->payload, cur->size); + ptr += cur->size; + written += cur->size; + } + + /* pad with zeros if needed */ + zeros = ROUNDUP(cur->size, 4) - cur->size; + if (zeros) { + memset(ptr, 0, zeros); + ptr += zeros; + written += zeros; + } + + pr_debug("written %zu bytes for utag [%s]\n", + written, cur->name); + + if (!strncmp(cur->name, UTAG_TAIL, MAX_UTAG_NAME)) + break; + + cur = cur->next; + } + + memset(ptr, 0, buf + frozen_size - ptr); + if ((buf + frozen_size - ptr)) + pr_debug("padded %zu bytes\n", buf + frozen_size - ptr); + if (tags_size) + *tags_size = frozen_size; + + return buf; +} + +/* + * Try to load utags into memory from a partition on secondary storage. + * + * Not thread safe, call from a safe context only + */ +static struct utag *load_utags(struct blkdev *cb) +{ + size_t bytes; + int ret_bytes; + void *data; + struct utag *head = NULL; + struct ctrl *ctrl = container_of(cb, struct ctrl, main); + loff_t pos = 0; + + if (open_utags(cb)) + return NULL; + + bytes = data_size(cb); + + /* + * make sure the block is at least big enough to hold header + * and footer, create empty partition for hwtags only + */ + if (UTAG_MIN_TAG_SIZE * 2 > bytes) { + pr_err("[%s] invalid tags size %zu\n", ctrl->dir_name, bytes); + if (!ctrl->hwtag) + return NULL; + if (init_empty(ctrl)) + return NULL; + bytes = UTAG_MIN_TAG_SIZE * 2; + } + + data = vmalloc(bytes); + if (!data) + return NULL; + + ret_bytes = kernel_read(cb->filep, data, bytes, &pos); + if (bytes != ret_bytes) { + pr_err("(%s) read failed ret %d\n", cb->name, ret_bytes); + goto free_data; + } + + head = thaw_tags(bytes, data); + if (!head && ctrl->hwtag) + init_empty(ctrl); + + /* Save pointer to the root attributes UTAG if present */ + if (ctrl->hwtag) { + ctrl->attrib = find_first_utag(head, ".attributes"); + pr_debug(" .attributes %s\n", ctrl->attrib ? + "found" : "not found"); + ctrl->features = find_first_utag(head, ".features"); + pr_debug(" .features %s\n", ctrl->features ? + "found" : "not found"); + } + + free_data: + vfree(data); + return head; +} + +/* + * Wrapper to call load_utags as a work function + * + */ +void load_work_func(struct work_struct *work) +{ + struct ctrl *ctrl = container_of(work, struct ctrl, load_work); + + ctrl->head = load_utags(&ctrl->main); + complete(&ctrl->load_comp); +} + +static int full_utag_name(struct proc_node *pnode, char *tag) +{ + int i, subdir, blen; + char *subdir_names[UTAG_DEPTH]; + struct ctrl *ctrl = pnode->ctrl; + struct proc_dir_entry *parent; + struct dir_node *c, *dir_node; + + *tag = 0; + for (subdir = 0, parent = pnode->dir; parent;) { + dir_node = NULL; + list_for_each_entry(c, &ctrl->dir_list, entry) { + if (c->dir == parent) { + dir_node = c; + break; + } + } + + if (!dir_node) + break; + + pr_debug("dir [%s] has %sparent\n", dir_node->name, + dir_node->parent == ctrl->root ? "no " : ""); + + subdir_names[subdir++] = dir_node->name; + parent = dir_node->parent; + } + + pr_debug("utag consists of %d subdirs\n", subdir); + + /* apply subdirs in opposite order */ + for (blen = 0, i = subdir - 1; i >= 0; i--) + blen += scnprintf(tag + blen, MAX_UTAG_NAME - blen, + "%s%s", subdir_names[i], i ? "/" : ""); + + if (pnode->type[0] != 0) + /* top off with type strings */ + blen += scnprintf(tag + blen, MAX_UTAG_NAME - blen, + ":%s", pnode->type); + + pr_debug("full name [%s](%d) has %d subdirs\n", tag, blen, subdir); + + return blen; +} + +static int check_utag_range(char *tag, struct utag *head, char *data, + size_t count) +{ + char rtag[MAX_UTAG_NAME]; + struct utag *range; + char *buf, *ptr, *tok; + size_t check; + bool found = false; + size_t len, data_len; + + /* copy utag name and append .range */ + strlcpy(rtag, tag, MAX_UTAG_NAME); + tok = strnchr(rtag, MAX_UTAG_NAME, ':'); + if (tok) + *tok = 0; + if (strlcat(rtag, "/.range", MAX_UTAG_NAME) > MAX_UTAG_NAME) { + pr_err("full name [%s] .range error\n", rtag); + return -EIO; + } + + pr_debug("utag range check [%s]\n", rtag); + + /* walk the thawed list to find the range */ + range = find_first_utag(head, rtag); + if (!range) { + pr_debug("full name [%s] no .range\n", rtag); + return 0; + } + + if (!range->size) { + pr_debug("full name [%s] .range not set\n", rtag); + return 0; + } + + /* check sizes and termination of the data */ + check = strnlen(range->payload, range->size); + if (check == range->size) { + pr_err("range payload for [%s] is not null terminated\n", rtag); + return -EIO; + } + + if (count >= check) { + pr_err("data for [%s] is not allowed %zu > %zu\n", tag, count, check); + return -EIO; + } + + data_len = strnlen(data, count); + if (count == data_len) { + pr_err("data for [%s] not a string\n", tag); + return -EIO; + } + + /* make local copy of .range payload to tokenize */ + buf = ptr = kzalloc(range->size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + memcpy(buf, range->payload, range->size); + + while (ptr && !found) { + tok = strsep(&ptr, ","); + len = strnlen(tok, MAX_UTAG_SIZE); + pr_debug("range value [%s] for [%s]\n", tok, rtag); + /* we can skip right away if length does not match */ + if (data_len != len) + continue; + if (!strncmp(tok, data, len)) + found = true; + } + kfree(buf); + + /* if ptr is still NULL we did not find a match */ + if (!found) { + pr_err("data [%s] out of range @[%s]\n", data, rtag); + return -EINVAL; + } + pr_debug("data [%s] in range\n", data); + + return 0; +} + +static int replace_first_utag(struct utag *head, char *name, + void *payload, size_t size) +{ + struct utag *utag; + void *oldpayload; + + /* search for the first occurrence of specified type of tag */ + utag = find_first_utag(head, name); + if (!utag) + return 0; + + oldpayload = utag->payload; + if (utag->flags & UTAG_FLAG_PROTECTED) { + pr_err("protected utag %s\n", name); + return -EIO; + } + + utag->payload = kzalloc(size, GFP_KERNEL); + if (!utag->payload) { + utag->payload = oldpayload; + return -EIO; + } + + memcpy(utag->payload, payload, size); + utag->size = size; + kfree(oldpayload); + return 0; +} + +static int store_utags(struct ctrl *ctrl, struct utag *tags) +{ + size_t written; + size_t tags_size; + char *datap = NULL; + int rc = 0; + mm_segment_t fs; + loff_t pos = 0; + struct file *fp; + struct blkdev *cb = &ctrl->main; + + fs = get_fs(); + set_fs(KERNEL_DS); + + pr_debug("[%s] utags partition blk_sz=%zu\n", ctrl->dir_name, cb->size); + + datap = freeze_tags(cb->size, tags, &tags_size); + if (!datap) { + rc = -EIO; + goto out; + } + + if (open_utags(cb)) { + rc = -EIO; + goto out; + } + fp = cb->filep; + + written = vfs_write(fp, datap, tags_size, &pos); + if (written < tags_size) { + pr_err("failed to write file (%s), rc=%zu\n", + cb->name, written); + rc = -EIO; + } + + /* Only try to use backup partition if it is configured */ + if (ctrl->backup.name) { + cb = &ctrl->backup; + if (open_utags(cb)) + goto out; + fp = cb->filep; + pos = 0; + + written = vfs_write(fp, datap, tags_size, &pos); + if (written < tags_size) + pr_err("failed to write file (%s), rc=%zu\n", + cb->name, written); + } + vfree(datap); + + out: + set_fs(fs); + return rc; +} + +/* + * Wrappers to call store_utags as a work function + * + */ +void store_work_func(struct work_struct *work) +{ + int rc; + struct ctrl *ctrl = container_of(work, struct ctrl, store_work); + + rc = store_utags(ctrl, ctrl->head); + if (rc) + pr_err("error storing utags partition\n"); + complete(&ctrl->store_comp); +} + +static int read_utag(struct seq_file *file, void *v) +{ + int i, error; + char utag_name[MAX_UTAG_NAME]; + uint8_t *ptr; + struct utag *tags = NULL; + struct utag *tag = NULL; + struct proc_node *proc = (struct proc_node *)file->private; + struct ctrl *ctrl = proc->ctrl; + int rc = 0; + + mutex_lock(&ctrl->access_lock); + queue_work(ctrl->load_queue, &ctrl->load_work); + wait_for_completion(&ctrl->load_comp); + tags = ctrl->head; + if (NULL == tags) { + pr_err("load utags error\n"); + mutex_unlock(&ctrl->access_lock); + return -EFAULT; + } + + /* traverse back all parent directories up to root */ + error = full_utag_name(proc, utag_name); + if (!error) { + seq_puts(file, "cannot find utag associated with this file\n"); + rc = -EINVAL; + goto free_tags_exit; + } + + tag = find_first_utag(tags, utag_name); + if (NULL == tag) { + seq_printf(file, "utag [%s] not found\n", utag_name); + rc = -EINVAL; + goto free_tags_exit; + } + + switch (proc->mode) { + case OUT_ASCII: + seq_printf(file, "%s", (char *)tag->payload); + break; + case OUT_RAW: + ptr = (uint8_t *) tag->payload; + for (i = 0; i < tag->size; i++) + seq_printf(file, "%02X", *(ptr + i)); + break; + case OUT_TYPE: + if (*(char *)proc->type != 0) + seq_printf(file, "%s", (char *)proc->type); + break; + } + seq_puts(file, "\n"); + +free_tags_exit: + free_tags(tags); + mutex_unlock(&ctrl->access_lock); + return rc; +} + +static ssize_t write_utag(struct file *file, const char __user *buffer, + size_t count, loff_t *pos) +{ + int i, error; + char *payload, utag[MAX_UTAG_NAME]; + struct utag *tags = NULL; + struct inode *inode = file_inode(file); + struct proc_node *proc = PDE_DATA(inode); + struct ctrl *ctrl = proc->ctrl; + size_t lco, length = count; + + pr_debug("%zu bytes write attempt to [%s](%d)\n", + count, proc->name, proc->mode); + if (OUT_TYPE == proc->mode) { + return -EINVAL; + } + + if (MAX_UTAG_SIZE < count) { + pr_err("error utag too big %zu\n", count); + return -EINVAL; + } + /* allocate an extra byte in case we need to add null-byte */ + payload = kzalloc(count + 1, GFP_KERNEL); + if (!payload) + return -ENOMEM; + + if (copy_from_user(payload, buffer, count)) { + pr_err("user copy error\n"); + count = -EIO; + goto free_temp_exit; + } + + lco = count - 1; /* last character offset */ + if (payload[lco] == '\n') { + payload[lco] = 0; + /* update offset only if input has anything else, but '\n' */ + if (lco) + lco--; + length--; + pr_debug("replace trailing newline with null-byte\n"); + } + /* make sure ASCII input is properly terminated */ + if (proc->mode == OUT_ASCII) { + for (i = 0; i <= lco; i++) + if (payload[i] == 0) + break; + if (i > lco) { + payload[++lco] = 0; + length++; + pr_debug("add trailing null-byte\n"); + } + } + + mutex_lock(&ctrl->access_lock); + queue_work(ctrl->load_queue, &ctrl->load_work); + wait_for_completion(&ctrl->load_comp); + tags = ctrl->head; + if (NULL == tags) { + pr_err("[%s] load error\n", ctrl->dir_name); + count = -EIO; + goto free_temp_exit; + } + + if (ctrl->lock) { + pr_err("[%s] [%s] is locked\n", proc->name, ctrl->dir_name); + count = -EACCES; + goto free_tags_exit; + } + + /* traverse back all parent directories up to root */ + error = full_utag_name(proc, utag); + if (!error) { + pr_err("cannot find utag associated with this file\n"); + count = -EIO; + goto free_tags_exit; + } + + /* check if this utag has .range child only for hwtags */ + if (ctrl->hwtag && length) { + error = check_utag_range(utag, tags, payload, length); + if (error) { + count = -EINVAL; + goto free_tags_exit; + } + } + + error = replace_first_utag(tags, utag, payload, length); + if (error) { + pr_err("error storing [%s] new payload\n", utag); + count = -EIO; + goto free_tags_exit; + } + + queue_work(ctrl->store_queue, &ctrl->store_work); + wait_for_completion(&ctrl->store_comp); +free_tags_exit: + free_tags(tags); +free_temp_exit: + kfree(payload); + mutex_unlock(&ctrl->access_lock); + return count; +} + +static int rebuild_utags_directory(struct ctrl *ctrl) +{ + clear_utags_directory(ctrl); + return build_utags_directory(ctrl); +} + +/* + * Process delete file request. Check for existing utag, + * delete it, save utags, update proc fs +*/ + +static ssize_t delete_utag(struct file *file, const char __user *buffer, + size_t count, loff_t *pos) +{ + char *pattern, expendable[MAX_UTAG_NAME]; + struct utag *tags, *cur, *next; + struct inode *inode = file_inode(file); + struct ctrl *ctrl = PDE_DATA(inode); + + if ((MAX_UTAG_NAME < count) || (0 == count)) { + pr_err("invalid utag name %zu\n", count); + return -EIO; + } + + if (copy_from_user(expendable, buffer, count)) { + pr_err("user copy error\n"); + return -EFAULT; + } + + /* payload has input string plus \n. Replace \n with \0 */ + expendable[count-1] = 0; + if (!validate_name(expendable, count-1)) { + pr_err("invalid format %s\n", expendable); + return -EINVAL; + } + + if (check_path(expendable, count-1)) + return -EINVAL; + + mutex_lock(&ctrl->access_lock); + queue_work(ctrl->load_queue, &ctrl->load_work); + wait_for_completion(&ctrl->load_comp); + tags = ctrl->head; + if (NULL == tags) { + pr_err("[%s] load error\n", ctrl->dir_name); + mutex_unlock(&ctrl->access_lock); + return -EINVAL; + } + + if (ctrl->lock) { + pr_err("[%s] [%s] is locked\n", expendable, ctrl->dir_name); + count = -EACCES; + goto just_leave; + } + + cur = find_first_utag(tags, expendable); + if (!cur) { + pr_err("cannot find utag %s\n", expendable); + count = -EINVAL; + goto just_leave; + } + + /* update pointers */ + cur->prev->next = cur->next; + cur->next->prev = cur->prev; + kfree(cur); + pr_debug("deleted utag [%s]\n", expendable); + + /* remove all utags beneath */ + for (cur = tags->next; cur->next;) { + pattern = strnstr(cur->name, expendable, MAX_UTAG_NAME); + /* any subutags will start with the suffix followed by a '/' */ + if ((pattern == cur->name) && (cur->name[count-1] == '/')) { + pr_debug("deleting utag [%s]\n", cur->name); + next = cur->next; + cur->prev->next = cur->next; + cur->next->prev = cur->prev; + kfree(cur); + cur = next; + continue; + } + cur = cur->next; + } + + /* Store changed partition */ + queue_work(ctrl->store_queue, &ctrl->store_work); + wait_for_completion(&ctrl->store_comp); + rebuild_utags_directory(ctrl); +just_leave: + free_tags(tags); + mutex_unlock(&ctrl->access_lock); + return count; +} + +static int lock_show(struct seq_file *file, void *v) +{ + struct ctrl *ctrl = (struct ctrl *)file->private; + + if (!ctrl) { + pr_err("no control data set\n"); + return -EIO; + } + + seq_printf(file, "%u\n", ctrl->lock); + return 0; +} + +/* Check utag name againts valid range saved in vld utag payload */ + +static int check_hwtag(struct ctrl *ctrl, char *attr, struct utag *vld) +{ + +char *buf, *ptr, *tok; +bool found = false; +size_t len, alen; + + if (!vld || !attr) { + pr_debug("no validation tag or data\n"); + return 0; + } + + if (!vld->size) { + pr_err("allow [%s] because [%s] is empty\n", attr, vld->name); + return 0; + } + + alen = strnlen(attr, MAX_UTAG_NAME); + pr_debug("name check for [%s] length [%zu]\n", attr, alen); + /* make local copy of validation payload to tokenize */ + buf = ptr = kzalloc(vld->size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + memcpy(ptr, vld->payload, vld->size); + + while (ptr && !found) { + tok = strsep(&ptr, ","); + /* *ptr is updated to show past the token */ + len = strnlen(tok, MAX_UTAG_NAME); + if (alen > len) + continue; + pr_debug("attr value [%s] len [%zu]\n", tok, len); + if (!strncmp(tok, attr, len)) + found = true; + } + kfree(buf); + + /* if ptr is still NULL we did not find a match */ + if (!found) { + pr_err("[%s] is not allowed\n", attr); + return -EINVAL; + } + + return 0; +} + +/* + * Process new file request. Check for existing utag, + * add empty new utag, save utags and add file interface +*/ + +static ssize_t new_utag(struct file *file, const char __user *buffer, + size_t count, loff_t *pos) +{ + struct inode *inode = file_inode(file); + struct ctrl *ctrl = PDE_DATA(inode); + struct utag *tags, *cur; + struct proc_dir_entry *parent = NULL; + char tree[UTAG_DEPTH][MAX_UTAG_NAME]; + char expendable[MAX_UTAG_NAME], *names[UTAG_DEPTH], *type = NULL; + int error, i, num_names; + size_t ret = count; + + if ((MAX_UTAG_NAME < count) || (0 == count)) { + pr_err("invalid utag name %zu\n", count); + return -EIO; + } + + if (copy_from_user(expendable, buffer, count)) { + pr_err("user copy error\n"); + return -EFAULT; + } + /* payload has input string plus \n. Replace \n with \0 */ + expendable[count-1] = 0; + if (!validate_name(expendable, count-1)) { + pr_err("invalid format %s\n", expendable); + return -EFAULT; + } + + if (check_path(expendable, count-1)) + return -EFAULT; + + if (no_show_tag(expendable)) { + pr_err("[%s] reserved name %s\n", ctrl->dir_name, expendable); + return -EFAULT; + } + + pr_debug("adding [%s] utag\n", expendable); + + mutex_lock(&ctrl->access_lock); + queue_work(ctrl->load_queue, &ctrl->load_work); + wait_for_completion(&ctrl->load_comp); + tags = ctrl->head; + if (NULL == tags) { + pr_err("[%s] load error\n", ctrl->dir_name); + mutex_unlock(&ctrl->access_lock); + return -EFAULT; + } + + if (ctrl->lock) { + pr_err("[%s] [%s] is locked\n", expendable, ctrl->dir_name); + ret = -EACCES; + goto just_leave; + } + + /* Ignore request if utag name already in use */ + cur = find_first_utag(tags, expendable); + if (NULL != cur) { + pr_err("cannot create [%s]; already in use\n", expendable); + ret = -EINVAL; + goto just_leave; + } + + + num_names = full_split(expendable, names, &type); + if (num_names == 0) { + pr_err("failed to split path\n"); + ret = -EINVAL; + goto just_leave; + } + + /* check if we are trying to add an attribute file */ + /* check every level of the path for validity */ + for (i = 0; i < num_names; i++) { + /* build name for each sublevel and add */ + /* slash to all sublevels except first */ + + scnprintf(tree[i], MAX_UTAG_NAME, "%s%s%s", i ? tree[i-1] : "", + i ? "/" : "", names[i]); + pr_debug("prepared subdir [%s] as [%s]\n", names[i], tree[i]); + + /* only check features for files that do not have a .*/ + if (ctrl->hwtag && '.' != names[i][0]) + if (check_hwtag(ctrl, tree[i], ctrl->features)) { + ret = -EINVAL; + goto just_leave; + } + } + + /* only check attributes for last file starting with a . */ + /* do not check for attributes in the root */ + if (ctrl->hwtag && num_names != 1 && '.' == names[num_names-1][0]) + if (check_hwtag(ctrl, names[num_names-1], ctrl->attrib)) { + ret = -EINVAL; + goto just_leave; + } + + for (i = 0; i < num_names; i++) { + error = add_utag_tail(tags, tree[i], type); + if (error == -EEXIST) { + struct dir_node *dnode; + /* need to update parent to ensure proper hierarchy */ + dnode = find_dir_node(ctrl, tree[i]); + parent = dnode->dir; + continue; + } + + /* create utag dir for every part of utag name */ + parent = proc_utag_dir(ctrl, names[i], tree[i], type, + true, parent); + if (IS_ERR(parent)) + break; + } + + walk_dir_nodes(ctrl); + walk_proc_nodes(ctrl); + + /* Store changed partition */ + queue_work(ctrl->store_queue, &ctrl->store_work); + wait_for_completion(&ctrl->store_comp); +just_leave: + free_tags(tags); + mutex_unlock(&ctrl->access_lock); + return ret; +} + +static int reload_show(struct seq_file *file, void *v) +{ + struct ctrl *ctrl = (struct ctrl *)file->private; + + if (!ctrl) + pr_err("no control data set\n"); + else + seq_printf(file, "%c\n", ctrl->reload); + + pr_debug("[%s] %c\n", ctrl->dir_name, ctrl->reload); + return 0; +} + +static ssize_t reload_write(struct file *file, const char __user *buffer, + size_t count, loff_t *pos) +{ + struct inode *inode = file_inode(file); + struct ctrl *ctrl = PDE_DATA(inode); + + + /* only single character input plus new line */ + if (2 < count) { + pr_err("invalid command length\n"); + return -EIO; + } + + mutex_lock(&ctrl->access_lock); + + if (UTAG_STATUS_LOADED == ctrl->reload) { + pr_info("[%s] (pid %i) [%s] already loaded\n", + current->comm, current->pid, ctrl->dir_name); + mutex_unlock(&ctrl->access_lock); + return count; + } + + if (copy_from_user(&ctrl->reload, buffer, 1)) { + pr_err("user copy error\n"); + mutex_unlock(&ctrl->access_lock); + return -EFAULT; + } + + pr_info("[%s] (pid %i) [%s] %c\n", + current->comm, current->pid, ctrl->dir_name, ctrl->reload); + + if (UTAG_STATUS_RELOAD == ctrl->reload) { + if (rebuild_utags_directory(ctrl)) + ctrl->reload = UTAG_STATUS_FAILED; + } + + mutex_unlock(&ctrl->access_lock); + return count; +} + +static int lock_open(struct inode *inode, struct file *file) +{ + return single_open(file, lock_show, PDE_DATA(inode)); +} + +static int reload_open(struct inode *inode, struct file *file) +{ + return single_open(file, reload_show, PDE_DATA(inode)); +} + +static int partition_open(struct inode *inode, struct file *file) +{ + return single_open(file, read_utag, PDE_DATA(inode)); +} + +static const struct file_operations reload_fops = { + .owner = THIS_MODULE, + .open = reload_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = reload_write, +}; + +static int build_utags_directory(struct ctrl *ctrl) +{ + struct proc_dir_entry *parent; + struct utag *tags, *cur; + int rc = 0; + + /* try to load utags from primary partition */ + queue_work(ctrl->load_queue, &ctrl->load_work); + wait_for_completion(&ctrl->load_comp); + tags = ctrl->head; + if (NULL == tags) { + pr_err("[%s] load error\n", ctrl->dir_name); + return -EIO; + } + /* skip utags head */ + cur = tags->next; + while (1) { + int i, num_names; + char expendable[MAX_UTAG_NAME]; + char *type, *names[UTAG_DEPTH]; + char path[MAX_UTAG_NAME]; + + memset(path, 0, MAX_UTAG_NAME); + + /* hook to skip special tags if we have any*/ + if (no_show_tag(cur->name)) { + cur = cur->next; + continue; + } + + /* skip utags tail */ + if (cur->next == NULL) + break; + + memcpy(expendable, cur->name, MAX_UTAG_NAME); + parent = NULL, type = NULL; + num_names = full_split(expendable, names, &type); + + for (i = 0; i < num_names; i++) { + scnprintf(path + strlen(path), + MAX_UTAG_NAME - strlen(path), + "%s%s", i ? "/" : "", names[i]); + pr_debug("creating subdir %s as %s\n", names[i], path); + parent = proc_utag_dir(ctrl, names[i], path, type, + /* populate only utag dir */ + (i == (num_names - 1)) ? true : false, + parent); + if (IS_ERR(parent)) { + rc = -EINVAL; + goto stop_building_utags; + } + } + + cur = cur->next; + } + +stop_building_utags: + + walk_dir_nodes(ctrl); + walk_proc_nodes(ctrl); + + free_tags(tags); + if (!rc) + ctrl->reload = UTAG_STATUS_LOADED; + return rc; +} + +#ifdef CONFIG_OF +static int utags_dt_init(struct platform_device *pdev) +{ + int rc; + struct device_node *node = pdev->dev.of_node; + struct ctrl *ctrl; + + ctrl = dev_get_drvdata(&pdev->dev); + rc = of_property_read_string(node, "mmi,main-utags", + &ctrl->main.name); + if (rc) { + pr_err("storage path not provided\n"); + return -EIO; + } + + rc = of_property_read_string(node, "mmi,backup-utags", + &ctrl->backup.name); + if (rc) + pr_info("backup storage path not provided\n"); + + ctrl->dir_name = DEFAULT_ROOT; + rc = of_property_read_string(node, "mmi,dir-name", &ctrl->dir_name); + if (!rc) + pr_debug("utag dir override %s\n", ctrl->dir_name); + + return 0; +} +#else +static int utags_dt_init(struct platform_device *pdev) { return -EINVAL; } +#endif + +static void clear_utags_directory(struct ctrl *ctrl) +{ + struct proc_node *node, *s = NULL; + struct dir_node *dir_node, *c = NULL; + + list_for_each_entry_safe(dir_node, c, &ctrl->dir_list, entry) { + if (dir_node->parent != ctrl->root) + continue; + /* remove whole subtree of first level subdir */ + remove_proc_subtree(dir_node->name, ctrl->root); + + pr_debug("removing subtree [%s]\n", dir_node->name); + + list_del(&dir_node->entry); + kfree(dir_node); + } + + /* all subtrees removed; just free memory */ + list_for_each_entry_safe(dir_node, c, &ctrl->dir_list, entry) { + list_del(&dir_node->entry); + kfree(dir_node); + } + + /* nothing left in procfs except reload file; free memory */ + list_for_each_entry_safe(node, s, &ctrl->node_list, entry) { + list_del(&node->entry); + kfree(node); + } +} + + +#define UTAGS_QNAME_SIZE 10 +static int utags_probe(struct platform_device *pdev) +{ + int rc; + struct ctrl *ctrl; + char buf[UTAGS_QNAME_SIZE]; + + ctrl = devm_kzalloc(&pdev->dev, sizeof(struct ctrl), GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + + INIT_LIST_HEAD(&ctrl->dir_list); + INIT_LIST_HEAD(&ctrl->node_list); + ctrl->pdev = pdev; + ctrl->reload = UTAG_STATUS_NOT_READY; + mutex_init(&ctrl->access_lock); + + init_completion(&ctrl->load_comp); + init_completion(&ctrl->store_comp); + INIT_WORK(&ctrl->load_work, load_work_func); + INIT_WORK(&ctrl->store_work, store_work_func); + + dev_set_drvdata(&pdev->dev, ctrl); + + rc = utags_dt_init(pdev); + if (rc) + return -EIO; + + scnprintf(buf, UTAGS_QNAME_SIZE, "%s_load", ctrl->dir_name); + ctrl->load_queue = create_workqueue(buf); + if (!ctrl->load_queue) { + pr_err("Failed to create workqueue %s\n", buf); + return -ENOMEM; + } + + scnprintf(buf, UTAGS_QNAME_SIZE, "%s_store", ctrl->dir_name); + ctrl->store_queue = create_workqueue(buf); + if (!ctrl->store_queue) { + destroy_workqueue(ctrl->load_queue); + pr_err("Failed to create workqueue %s\n", buf); + return -ENOMEM; + } + + ctrl->root = proc_mkdir(ctrl->dir_name, NULL); + if (!ctrl->root) { + destroy_workqueue(ctrl->load_queue); + destroy_workqueue(ctrl->store_queue); + pr_err("Failed to create dir entry\n"); + return -EIO; + } + + if (!strncmp(ctrl->dir_name, HW_ROOT, sizeof(HW_ROOT))) + ctrl->hwtag = 1; + + if (!proc_create_data("reload", 0600, ctrl->root, &reload_fops, ctrl)) { + pr_err("Failed to create reload entry\n"); + destroy_workqueue(ctrl->load_queue); + destroy_workqueue(ctrl->store_queue); + remove_proc_subtree(ctrl->dir_name, NULL); + return -EIO; + } + + if (proc_utag_util(ctrl)) { + pr_err("Failed to create util dir\n"); + destroy_workqueue(ctrl->load_queue); + destroy_workqueue(ctrl->store_queue); + remove_proc_subtree(ctrl->dir_name, NULL); + return -EFAULT; + } + + pr_info("Done [%s]\n", ctrl->dir_name); + return 0; +} + +static int utags_remove(struct platform_device *pdev) +{ + struct ctrl *ctrl = dev_get_drvdata(&pdev->dev); + + clear_utags_directory(ctrl); + remove_proc_subtree(ctrl->dir_name, NULL); + destroy_workqueue(ctrl->load_queue); + destroy_workqueue(ctrl->store_queue); + if (ctrl->main.filep) + filp_close(ctrl->main.filep, NULL); + if (ctrl->backup.filep) + filp_close(ctrl->backup.filep, NULL); + return 0; +} + +#ifdef CONFIG_OF +static struct of_device_id utags_match_table[] = { + { .compatible = "mmi,utags", + }, + {} +}; +#endif + +static struct platform_driver utags_driver = { + .probe = utags_probe, + .remove = utags_remove, + .driver = { + .name = DRVNAME, + .bus = &platform_bus_type, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(utags_match_table), + }, +}; + +static int __init utags_init(void) +{ + return platform_driver_register(&utags_driver); +} + +static void __exit utags_exit(void) +{ + platform_driver_unregister(&utags_driver); +} + +late_initcall(utags_init); +module_exit(utags_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Motorola Mobility LLC"); +MODULE_DESCRIPTION("Configuration module"); -- 2.20.1