blob: f23e5318a1c83d1475de4b079c93046ebdb2d5d6 [file] [log] [blame]
/* Copyright (c) 2012, 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.
*
*/
#include <linux/fcntl.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/ioctl.h>
#include <linux/input.h>
#include <linux/inet.h>
#include <linux/list.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/platform_device.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/string.h>
#include <linux/stat.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/unistd.h>
#include <linux/slab.h>
#define MAX_UTAG_SIZE 1024
#define MAX_UTAG_NAME 32
#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)
static const struct file_operations utag_fops;
enum utag_flag {
UTAG_FLAG_PROTECTED = 1 << 0,
};
#define UTAG_STATUS_LOADED '0'
#define UTAG_STATUS_RELOAD '1'
#define UTAG_STATUS_NOT_READY '2'
static uint32_t csum;
static char reload_ctrl = UTAG_STATUS_NOT_READY;
static char payload[MAX_UTAG_SIZE];
static struct proc_dir_entry *dir_root;
static char utags_blkdev[255];
static char utags_backup[255];
module_param_string(blkdev, utags_blkdev, sizeof(utags_blkdev), 0664);
MODULE_PARM_DESC(blkdev, "Full path for utags partition");
module_param_string(backup, utags_backup, sizeof(utags_backup), 0664);
MODULE_PARM_DESC(blkdev, "Full path for utags backup partition");
struct utag;
struct utag {
char name[MAX_UTAG_NAME];
uint32_t size;
uint32_t flags;
uint32_t crc32; /* reserved for futher implementation */
void *payload;
struct utag *next;
};
struct frozen_utag {
char name[MAX_UTAG_NAME];
uint32_t size;
uint32_t flags;
uint32_t crc32;
uint8_t payload[];
};
#define UTAG_MIN_TAG_SIZE (sizeof(struct frozen_utag))
enum utag_output {
OUT_ASCII = 0,
OUT_RAW,
OUT_TYPE,
OUT_NEW
};
char *files[] = {
"ascii",
"raw",
"type",
"new"
};
struct proc_node {
struct list_head entry;
char full_name[MAX_UTAG_NAME];
char name[MAX_UTAG_NAME];
char type[MAX_UTAG_NAME];
char file_name[MAX_UTAG_NAME];
struct proc_dir_entry *file;
struct proc_dir_entry *dir;
uint32_t mode;
};
LIST_HEAD(node_list);
struct dir_node {
struct list_head entry;
struct proc_dir_entry *root;
char name[MAX_UTAG_NAME];
};
LIST_HEAD(dir_list);
enum utag_error {
UTAG_NO_ERROR = 0,
UTAG_ERR_NO_ROOM,
UTAG_ERR_OUT_OF_MEMORY,
UTAG_ERR_INVALID_HEAD,
UTAG_ERR_INVALID_TAIL,
UTAG_ERR_TAGS_TOO_BIG,
UTAG_ERR_PARTITION_OPEN_FAILED,
UTAG_ERR_PARTITION_NOT_BLKDEV,
UTAG_ERR_PARTITION_READ_ERR,
UTAG_ERR_PARTITION_WRITE_ERR,
UTAG_ERR_NOT_FOUND,
UTAG_ERR_INVALID_ARGS,
UTAG_ERR_PROTECTED,
};
static void build_utags_directory(void);
static void clear_utags_directory(void);
/*
* 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;
}
}
/*
* compare only the name part of a <name[:type]> formatted full
* utag name
*
* 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;
while (count--) {
c1 = *s1++;
c2 = *s2++;
if (c1 == ':')
c1 = 0;
if (c2 == ':')
c2 = 0;
r = c1 - c2;
if (r || !c1)
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;
}
/*
* split long name <name[:type]> of a utag in two
* name and type
*
* if there is no : separator type will be empty string
*/
static inline void split_name(char *full, char *name, char *type)
{
size_t pos, type_length;
memset(name, 0, MAX_UTAG_NAME);
memset(type, 0, MAX_UTAG_NAME);
for (pos = 0; pos < MAX_UTAG_NAME; pos++) {
if ((full[pos] == ':') || (full[pos] == 0))
break;
}
memcpy(name, full, pos);
type_length = strnlen(full, MAX_UTAG_NAME) - 1 - pos;
if (0 < type_length)
memcpy(type, (full + pos + 1), type_length);
}
/*
* Find first instance of utag by specified name
*/
static struct utag *find_first_utag(const struct utag *head, const char *name)
{
struct utag *cur;
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
utag_file(char *utag_name, char *utag_type,
enum utag_output mode, struct proc_dir_entry *dir,
const struct file_operations *fops)
{
struct proc_node *node;
if (sizeof(files) < mode)
return -EINVAL;
node = kmalloc(sizeof(struct proc_node), GFP_KERNEL);
if (node) {
list_add(&node->entry, &node_list);
strlcpy(node->file_name, files[mode], MAX_UTAG_NAME);
strlcpy(node->name, utag_name, MAX_UTAG_NAME);
strlcpy(node->type, utag_type, MAX_UTAG_NAME);
node->mode = mode;
node->dir = dir;
node->file = proc_create_data(node->file_name, 0, dir, fops, node);
}
return 0;
}
/*
* 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,
enum utag_error *status)
{
struct utag *head = NULL, *cur = NULL;
uint8_t *ptr = buf;
enum utag_error err = UTAG_NO_ERROR;
/*
* make sure the block is at least big enough to hold header
* and footer
*/
if (UTAG_MIN_TAG_SIZE * 2 > block_size) {
err = UTAG_ERR_NO_ROOM;
goto out;
}
while (1) {
struct frozen_utag *frozen;
uint8_t *next_ptr;
frozen = (struct frozen_utag *)ptr;
if (!head) {
/* calloc zeros allocated memory */
cur = kcalloc(1, sizeof(struct utag), GFP_KERNEL);
if (!cur) {
err = UTAG_ERR_OUT_OF_MEMORY;
goto out;
}
}
strlcpy(cur->name, frozen->name, MAX_UTAG_NAME - 1);
cur->flags = ntohl(frozen->flags);
cur->size = ntohl(frozen->size);
if (!head) {
head = cur;
if (strcmp(head->name, UTAG_HEAD) ||
(0 != head->size)) {
err = UTAG_ERR_INVALID_HEAD;
goto err_free;
}
}
/* check if this is the end */
if (!strcmp(cur->name, UTAG_TAIL)) {
/* footer payload size should be zero */
if (0 != cur->size) {
err = UTAG_ERR_INVALID_TAIL;
goto err_free;
}
/* all done */
break;
}
next_ptr = ptr + UTAG_MIN_TAG_SIZE + ROUNDUP(cur->size, 4);
/*
* 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) {
err = UTAG_ERR_TAGS_TOO_BIG;
goto err_free;
}
if (cur->size != 0) {
cur->payload = kcalloc(1, cur->size, GFP_KERNEL);
if (!cur->payload) {
err = UTAG_ERR_OUT_OF_MEMORY;
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, calloc() sets memory to zero */
cur->next = kcalloc(1, sizeof(struct utag), GFP_KERNEL);
cur = cur->next;
if (!cur) {
err = UTAG_ERR_OUT_OF_MEMORY;
goto err_free;
}
} /* while (1) */
goto out;
err_free:
free_tags(head);
head = NULL;
out:
if (status)
*status = err;
return head;
}
static void *freeze_tags(size_t block_size, const struct utag *tags,
size_t *tags_size, enum utag_error *status)
{
size_t frozen_size = 0;
char *buf = NULL, *ptr;
const struct utag *cur = tags;
enum utag_error err = UTAG_NO_ERROR;
size_t zeros;
struct frozen_utag frozen;
/* Make sure the tags start with the HEAD marker. */
if (!tags || strncmp(tags->name, UTAG_HEAD, MAX_UTAG_NAME)) {
err = UTAG_ERR_INVALID_HEAD;
goto out;
}
/*
* Walk the list once to determine the amount of space to allocate
* for the frozen tags.
*/
while (cur) {
frozen_size += ROUNDUP(cur->size, 4) + UTAG_MIN_TAG_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);
/* do some more sanity checking */
if (!cur || cur->next) {
err = UTAG_ERR_INVALID_TAIL;
goto out;
}
if (block_size < frozen_size) {
err = UTAG_ERR_TAGS_TOO_BIG;
goto out;
}
ptr = buf = kmalloc(frozen_size, GFP_KERNEL);
if (!buf) {
err = UTAG_ERR_OUT_OF_MEMORY;
goto out;
}
cur = tags;
while (1) {
memcpy(frozen.name, cur->name, MAX_UTAG_NAME);
frozen.flags = htonl(cur->flags);
frozen.size = htonl(cur->size);
memcpy(ptr, &frozen, UTAG_MIN_TAG_SIZE);
ptr += UTAG_MIN_TAG_SIZE;
if (cur->size) {
memcpy(ptr, cur->payload, cur->size);
ptr += cur->size;
}
/* pad with zeros if needed */
zeros = ROUNDUP(cur->size, 4) - cur->size;
if (zeros) {
memset(ptr, 0, zeros);
ptr += zeros;
}
if (!strncmp(cur->name, UTAG_TAIL, MAX_UTAG_NAME))
break;
cur = cur->next;
}
memset(ptr, 0, buf + frozen_size - ptr);
if (tags_size)
*tags_size = frozen_size;
out:
if (status)
*status = err;
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(const char *partition_name)
{
size_t block_size;
ssize_t bytes;
struct utag *head = NULL;
void *data;
int file_errno;
struct file *filp = NULL;
struct inode *inode = NULL;
if (0 == partition_name[0]) {
pr_err("%s utag partition not set\n", __func__);
goto out;
}
filp = filp_open(partition_name, O_RDONLY, 0600);
if (IS_ERR_OR_NULL(filp)) {
file_errno = -PTR_ERR(filp);
pr_err("%s ERR opening (%s) errno=%d\n", __func__,
partition_name, file_errno);
goto out;
}
if (filp->f_path.dentry)
inode = filp->f_path.dentry->d_inode;
if (!inode || !S_ISBLK(inode->i_mode)) {
pr_err("%s ERR (%s) not a block device\n", __func__,
partition_name);
goto close_block;
}
block_size = i_size_read(inode->i_bdev->bd_inode);
data = kmalloc(block_size, GFP_KERNEL);
if (!data) {
pr_err("%s ERR file (%s) out of memory size %d\n", __func__,
partition_name, block_size);
goto close_block;
}
bytes = kernel_read(filp, filp->f_pos, data, block_size);
if ((ssize_t) block_size > bytes) {
pr_err("%s ERR file (%s) read failed\n", __func__,
partition_name);
goto free_data;
}
head = thaw_tags(block_size, data, NULL);
free_data:
kfree(data);
close_block:
filp_close(filp, NULL);
out:
return head;
}
static enum utag_error
replace_first_utag(struct utag *head, const char *name, void *payload,
size_t size)
{
struct utag *utag;
/* search for the first occurrence of specified type of tag */
utag = find_first_utag(head, name);
if (utag) {
void *oldpayload = utag->payload;
if (utag->flags & UTAG_FLAG_PROTECTED)
return UTAG_ERR_PROTECTED;
utag->payload = kmalloc(size, GFP_KERNEL);
if (!utag->payload) {
utag->payload = oldpayload;
return UTAG_ERR_OUT_OF_MEMORY;
}
memcpy(utag->payload, payload, size);
utag->size = size;
kfree(oldpayload);
return UTAG_NO_ERROR;
}
return UTAG_NO_ERROR;
}
static enum utag_error
flash_partition(const char *partition_name, const struct utag *tags)
{
size_t written = 0;
size_t block_size = 0, tags_size = 0;
char *datap = NULL;
enum utag_error status = UTAG_NO_ERROR;
int file_errno;
struct file *filep = NULL;
struct inode *inode = NULL;
mm_segment_t fs;
fs = get_fs();
set_fs(KERNEL_DS);
filep = filp_open(partition_name, O_RDWR|O_SYNC, 0);
if (IS_ERR_OR_NULL(filep)) {
file_errno = -PTR_ERR(filep);
pr_err("%s ERROR opening file (%s) errno=%d\n", __func__,
partition_name, file_errno);
status = UTAG_ERR_PARTITION_OPEN_FAILED;
goto out;
}
if (filep->f_path.dentry)
inode = filep->f_path.dentry->d_inode;
if (!inode || !S_ISBLK(inode->i_mode)) {
status = UTAG_ERR_PARTITION_NOT_BLKDEV;
goto close_block;
}
block_size = i_size_read(inode->i_bdev->bd_inode);
datap = freeze_tags(block_size, tags, &tags_size, &status);
if (!datap)
goto close_block;
written = filep->f_op->write(filep, datap, tags_size, &filep->f_pos);
if (written < tags_size) {
pr_err("%s ERROR writing file (%s) ret %d\n", __func__,
utags_blkdev, written);
status = UTAG_ERR_PARTITION_WRITE_ERR;
}
kfree(datap);
close_block:
filp_close(filep, NULL);
out:
set_fs(fs);
return status;
}
static enum utag_error store_utags(const struct utag *tags)
{
enum utag_error status = UTAG_NO_ERROR;
status = flash_partition(utags_blkdev, tags);
if (UTAG_NO_ERROR != status)
pr_err("%s flash partition failed status %d\n", __func__,
status);
if (0 != utags_backup[0]) {
status = flash_partition(utags_backup, tags);
if (UTAG_NO_ERROR != status)
pr_err("%s flash backup partition failed status %d\n",
__func__, status);
}
return status;
}
static int read_tag(struct seq_file *file, void *v)
{
int i;
uint8_t *tmp;
struct utag *tags = NULL;
struct utag *tag = NULL;
struct proc_node *proc = (struct proc_node *)file->private;
tags = load_utags(utags_blkdev);
if (NULL == tags) {
pr_err("%s Load config failed\n", __func__);
return -EFAULT;
} else {
tag = find_first_utag(tags, proc->name);
if (NULL == tag) {
pr_err("Tag [%s] not found.\n", proc->name);
free_tags(tags);
return -EIO;
}
switch (proc->mode) {
case OUT_ASCII:
seq_printf(file, "%s\n", (char *)tag->payload);
break;
case OUT_RAW:
tmp = (uint8_t *) tag->payload;
for (i = 0; i < tag->size; i++)
seq_printf(file, "%02X", tmp[i]);
seq_printf(file, "\n");
break;
case OUT_TYPE:
seq_printf(file, "%s\n", (char *)proc->type);
break;
}
}
free_tags(tags);
return 0;
}
static ssize_t
write_utag(struct file *file, const char __user *buffer,
size_t count, loff_t *pos)
{
struct utag *tags = NULL;
enum utag_error status;
struct inode *inode = file->f_dentry->d_inode;
struct proc_node *proc = PDE_DATA(inode);
if (OUT_TYPE == proc->mode) {
return count;
}
if (MAX_UTAG_SIZE < count) {
pr_err("%s error utag too big %d\n", __func__, count);
return count;
}
if (copy_from_user(payload, buffer, count)) {
pr_err("%s user copy error\n", __func__);
return count;
}
tags = load_utags(utags_blkdev);
if (NULL == tags) {
pr_err("%s load config error\n", __func__);
return count;
} else {
status =
replace_first_utag(tags, proc->name, payload, (count - 1));
if (UTAG_NO_ERROR != status)
pr_err("%s error on update tag [%s] status %d\n",
__func__, proc->name, status);
else {
status = store_utags(tags);
if (UTAG_NO_ERROR != status)
pr_err
("%s error on store tags [%s] status %d\n",
__func__, proc->name, status);
}
}
free_tags(tags);
return count;
}
/*
* Process new file request. Check for exiisting 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 utag *tags, *cur;
enum utag_error status;
struct inode *inode = file->f_dentry->d_inode;
struct proc_node *proc = PDE_DATA(inode);
char uname[MAX_UTAG_NAME];
char utype[MAX_UTAG_NAME];
struct dir_node *dnode;
struct proc_dir_entry *dir;
if ((MAX_UTAG_NAME < count) || (0 == count)) {
pr_err("%s invalid utag name %d\n", __func__, count);
return count;
}
if (copy_from_user(payload, buffer, count)) {
pr_err("%s user copy error\n", __func__);
return count;
}
/* payload has input string plus \n. Replace \n with 00 */
payload[count-1] = 0;
if (!validate_name(payload, (count-1))) {
pr_err("%s invalid format %s\n", __func__, payload);
return count;
}
tags = load_utags(utags_blkdev);
if (NULL == tags) {
pr_err("%s load config error\n", __func__);
return count;
} else {
/* Ignore request if utag name already in use */
cur = find_first_utag(tags, payload);
if (NULL != cur) {
pr_err("%s error can not create [%s]. Already in use\n",
__func__, payload);
goto out;
} else {
/* Add new utag after head, store changed partition */
cur = kcalloc(1, sizeof(struct utag), GFP_KERNEL);
if (!cur)
goto out;
strlcpy(cur->name, payload, MAX_UTAG_NAME);
split_name(payload, uname, utype);
cur->next = tags->next;
tags->next = cur;
status = store_utags(tags);
if (UTAG_NO_ERROR != status) {
pr_err
("%s error on store tags [%s] status %d\n",
__func__, proc->name, status);
goto out;
}
/* Add procfs elements for utag access */
dir = proc_mkdir(uname, dir_root);
if (!dir) {
pr_err("%s Failed to create dir\n", __func__);
goto out;
}
dnode = kmalloc(sizeof(struct dir_node), GFP_KERNEL);
if (dnode) {
dnode->root = dir_root;
list_add(&dnode->entry, &dir_list);
strlcpy(dnode->name, uname, MAX_UTAG_NAME);
}
utag_file(uname, utype, OUT_ASCII, dir, &utag_fops);
utag_file(uname, utype, OUT_RAW, dir, &utag_fops);
utag_file(uname, utype, OUT_TYPE, dir, &utag_fops);
}
}
out: free_tags(tags);
return count;
}
static int dump_all(struct seq_file *file, void *v)
{
int i;
char *data = NULL;
struct utag *tags = NULL;
uint32_t loc_csum = 0;
tags = load_utags(utags_blkdev);
if (NULL == tags)
pr_err("%s Load config failed\n", __func__);
else
while (tags != NULL) {
seq_printf(file,
"Tag: Name [%s] Size: [%d]\nData:\n\t",
tags->name, tags->size);
for (i = 0, data = tags->payload; i < tags->size; i++) {
loc_csum += data[i];
seq_printf(file, "[0x%02X] ", data[i]);
if ((i + 1) % 10 == 0)
seq_printf(file, "\n\t");
}
seq_printf(file, "\n\n");
tags = tags->next;
}
csum = loc_csum;
free_tags(tags);
return 0;
}
static int reload_show(struct seq_file *file, void *v)
{
seq_printf(file, "%c\n", reload_ctrl);
return 0;
}
static ssize_t reload_write(struct file *file, const char __user *buffer,
size_t count, loff_t *pos)
{
if (1 > count)
goto out;
if (copy_from_user(&reload_ctrl, buffer, 1)) {
pr_err("%s user copy error\n", __func__);
return -EFAULT;
}
if (UTAG_STATUS_RELOAD == reload_ctrl) {
clear_utags_directory();
build_utags_directory();
}
out:
return count;
}
static int config_read(struct inode *inode, struct file *file)
{
return single_open(file, read_tag, PDE_DATA(inode));
}
static int config_dump(struct inode *inode, struct file *file)
{
return single_open(file, dump_all, PDE_DATA(inode));
}
static int reload_open(struct inode *inode, struct file *file)
{
return single_open(file, reload_show, PDE_DATA(inode));
}
static const struct file_operations utag_fops = {
.owner = THIS_MODULE,
.open = config_read,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.write = write_utag,
};
static const struct file_operations dump_fops = {
.owner = THIS_MODULE,
.open = config_dump,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static const struct file_operations new_fops = {
.owner = THIS_MODULE,
.open = config_dump,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.write = new_utag,
};
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 void build_utags_directory(void)
{
struct utag *tags, *cur;
struct dir_node *dnode;
struct proc_dir_entry *dir = NULL;
char utag_name[MAX_UTAG_NAME];
char utag_type[MAX_UTAG_NAME];
/* try to load utags from primary partition */
tags = load_utags(utags_blkdev);
if (NULL == tags) {
pr_warn("%s Can not open utags\n", __func__);
return;
}
/* skip utags head */
cur = tags->next;
while (1) {
/* skip utags tail */
if (cur->next == NULL)
break;
split_name(cur->name, utag_name, utag_type);
dir = proc_mkdir(utag_name, dir_root);
if (!dir) {
pr_err("%s Failed to create dir\n", __func__);
break;
}
dnode = kmalloc(sizeof(struct dir_node), GFP_KERNEL);
if (dnode) {
dnode->root = dir_root;
list_add(&dnode->entry, &dir_list);
strlcpy(dnode->name, utag_name, MAX_UTAG_NAME);
}
utag_file(utag_name, utag_type, OUT_ASCII, dir, &utag_fops);
utag_file(utag_name, utag_type, OUT_RAW, dir, &utag_fops);
utag_file(utag_name, utag_type, OUT_TYPE, dir, &utag_fops);
cur = cur->next;
}
/* add "all" directory for debug purposes */
dir = proc_mkdir("all", dir_root);
dnode = kmalloc(sizeof(struct dir_node), GFP_KERNEL);
if (dnode) {
dnode->root = dir_root;
list_add(&dnode->entry, &dir_list);
strlcpy(dnode->name, "all", MAX_UTAG_NAME);
}
utag_file("all", "raw", OUT_RAW, dir, &dump_fops);
utag_file("all", "new", OUT_NEW, dir, &new_fops);
free_tags(tags);
reload_ctrl = UTAG_STATUS_LOADED;
}
static void clear_utags_directory(void)
{
struct proc_node *node, *s = NULL;
struct dir_node *dir_node, *c = NULL;
list_for_each_entry_safe(node, s, &node_list, entry) {
remove_proc_entry(node->file_name, node->dir);
list_del(&node->entry);
kfree(node);
}
list_for_each_entry_safe(dir_node, c, &dir_list, entry) {
remove_proc_entry(dir_node->name, dir_root);
list_del(&dir_node->entry);
kfree(dir_node);
}
}
static int __init config_init(void)
{
struct proc_dir_entry *reload_pde;
dir_root = proc_mkdir("config", NULL);
if (!dir_root) {
pr_err("%s Failed to create dir entry\n", __func__);
return -EIO;
}
reload_pde = proc_create("reload", 0600, dir_root, &reload_fops);
if (!reload_pde) {
pr_err("%s Failed to create reload entry\n", __func__);
remove_proc_entry("config", NULL);
return -EIO;
}
build_utags_directory();
return 0;
}
static void __exit config_exit(void)
{
clear_utags_directory();
remove_proc_entry("reload", dir_root);
remove_proc_entry("config", NULL);
}
module_init(config_init);
module_exit(config_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Motorola Mobility LLC");
MODULE_DESCRIPTION("Configuration module");