aboutsummaryrefslogtreecommitdiff
path: root/package/kernel/ubootenv-nvram/src/ubootenv-nvram.c
blob: f6244142167b0b9a266746e89de901f26f84dd9b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// SPDX-License-Identifier: GPL-2.0-only
/*
 *   Copyright (C) 2022  Bjørn Mork <bjorn@mork.no>
 */

#include <linux/fs.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/mtd/mtd.h>
#include <linux/of.h>
#include <linux/of_reserved_mem.h>
#include <linux/platform_device.h>

#define NAME "ubootenv"

struct ubootenv_drvdata {
	void *env;
	struct reserved_mem *rmem;
	struct miscdevice misc;
};

static inline struct ubootenv_drvdata *to_ubootenv_drvdata(struct file *file)
{
	return container_of(file->private_data, struct ubootenv_drvdata, misc);
}

static ssize_t ubootenv_write(struct file *file, const char __user *buffer, size_t count,
			      loff_t *ppos)
{
	struct ubootenv_drvdata *data = to_ubootenv_drvdata(file);

	if (!data->env)
		return -EIO;
	return simple_write_to_buffer(data->env, data->rmem->size, ppos, buffer, count);
}

static ssize_t ubootenv_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{
	struct ubootenv_drvdata *data = to_ubootenv_drvdata(file);

	if (!data->env)
		return 0;
	return simple_read_from_buffer(buffer, count, ppos, data->env, data->rmem->size);
}

static loff_t ubootenv_llseek(struct file *file, loff_t off, int whence)
{
	struct ubootenv_drvdata *data = to_ubootenv_drvdata(file);

	return fixed_size_llseek(file, off, whence, data->rmem->size);
}

/* Faking the minimal mtd ioctl subset required by the fw_env.c */
static long ubootenv_ioctl(struct file *file, u_int cmd, u_long arg)
{
	struct ubootenv_drvdata *data = to_ubootenv_drvdata(file);
	void __user *argp = (void __user *)arg;
	struct mtd_info_user info = {
		.type = MTD_NORFLASH,
		.size = data->rmem->size,
	};

	switch (cmd) {
	case MEMISLOCKED:
	case MEMERASE:
		break;
	case MEMGETINFO:
		if (copy_to_user(argp, &info, sizeof(struct mtd_info_user)))
			return -EFAULT;
		break;
	default:
		return -ENOTTY;
	}
	return 0;
}

static const struct file_operations ubootenv_fops = {
	.owner          = THIS_MODULE,
	.read           = ubootenv_read,
	.write          = ubootenv_write,
	.llseek         = ubootenv_llseek,
	.unlocked_ioctl = ubootenv_ioctl,
};

/* We can only map a single reserved-memory range */
static struct ubootenv_drvdata drvdata = {
	.misc = {
		.fops  = &ubootenv_fops,
		.minor = MISC_DYNAMIC_MINOR,
		.name  = NAME,
	},
};

const struct of_device_id of_ubootenv_match[] = {
	{ .compatible = "ubootenv" },
	{},
};
MODULE_DEVICE_TABLE(of, of_ubootenv_match);

static int ubootenv_probe(struct platform_device *pdev)
{
	struct ubootenv_drvdata *data = &drvdata;
	struct device *dev = &pdev->dev;
	struct device_node *np;

	/* enforce single instance */
	if (data->env)
		return -EINVAL;

	np = of_parse_phandle(dev->of_node, "memory-region", 0);
	if (!np)
		return -ENODEV;

	data->rmem = of_reserved_mem_lookup(np);
	of_node_put(np);
	if (!data->rmem)
		return -ENODEV;

	if (!data->rmem->size || (data->rmem->size > ULONG_MAX))
		return -EINVAL;

	if (!PAGE_ALIGNED(data->rmem->base) || !PAGE_ALIGNED(data->rmem->size))
		return -EINVAL;

	data->env = devm_memremap(&pdev->dev, data->rmem->base, data->rmem->size, MEMREMAP_WB);
	platform_set_drvdata(pdev, data);

	data->misc.parent = &pdev->dev;
	return misc_register(&data->misc);
}

static int ubootenv_remove(struct platform_device *pdev)
{
	struct ubootenv_drvdata *data = platform_get_drvdata(pdev);

	data->env = NULL;
	misc_deregister(&data->misc);
	return 0;
}

static struct platform_driver ubootenv_driver = {
	.probe = ubootenv_probe,
	.remove = ubootenv_remove,
	.driver = {
		.name           = NAME,
		.owner          = THIS_MODULE,
		.of_match_table = of_ubootenv_match,
	},
};

module_platform_driver(ubootenv_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Bjørn Mork <bjorn@mork.no>");
MODULE_DESCRIPTION("Access u-boot environment in RAM");