162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * CMOS/NV-RAM driver for Linux
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 1997 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
662306a36Sopenharmony_ci * idea by and with help from Richard Jelinek <rj@suse.de>
762306a36Sopenharmony_ci * Portions copyright (c) 2001,2002 Sun Microsystems (thockin@sun.com)
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * This driver allows you to access the contents of the non-volatile memory in
1062306a36Sopenharmony_ci * the mc146818rtc.h real-time clock. This chip is built into all PCs and into
1162306a36Sopenharmony_ci * many Atari machines. In the former it's called "CMOS-RAM", in the latter
1262306a36Sopenharmony_ci * "NVRAM" (NV stands for non-volatile).
1362306a36Sopenharmony_ci *
1462306a36Sopenharmony_ci * The data are supplied as a (seekable) character device, /dev/nvram. The
1562306a36Sopenharmony_ci * size of this file is dependent on the controller.  The usual size is 114,
1662306a36Sopenharmony_ci * the number of freely available bytes in the memory (i.e., not used by the
1762306a36Sopenharmony_ci * RTC itself).
1862306a36Sopenharmony_ci *
1962306a36Sopenharmony_ci * Checksums over the NVRAM contents are managed by this driver. In case of a
2062306a36Sopenharmony_ci * bad checksum, reads and writes return -EIO. The checksum can be initialized
2162306a36Sopenharmony_ci * to a sane state either by ioctl(NVRAM_INIT) (clear whole NVRAM) or
2262306a36Sopenharmony_ci * ioctl(NVRAM_SETCKS) (doesn't change contents, just makes checksum valid
2362306a36Sopenharmony_ci * again; use with care!)
2462306a36Sopenharmony_ci *
2562306a36Sopenharmony_ci * 	1.1	Cesar Barros: SMP locking fixes
2662306a36Sopenharmony_ci * 		added changelog
2762306a36Sopenharmony_ci * 	1.2	Erik Gilling: Cobalt Networks support
2862306a36Sopenharmony_ci * 		Tim Hockin: general cleanup, Cobalt support
2962306a36Sopenharmony_ci * 	1.3	Wim Van Sebroeck: convert PRINT_PROC to seq_file
3062306a36Sopenharmony_ci */
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci#define NVRAM_VERSION	"1.3"
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci#include <linux/module.h>
3562306a36Sopenharmony_ci#include <linux/nvram.h>
3662306a36Sopenharmony_ci#include <linux/types.h>
3762306a36Sopenharmony_ci#include <linux/errno.h>
3862306a36Sopenharmony_ci#include <linux/miscdevice.h>
3962306a36Sopenharmony_ci#include <linux/ioport.h>
4062306a36Sopenharmony_ci#include <linux/fcntl.h>
4162306a36Sopenharmony_ci#include <linux/mc146818rtc.h>
4262306a36Sopenharmony_ci#include <linux/init.h>
4362306a36Sopenharmony_ci#include <linux/proc_fs.h>
4462306a36Sopenharmony_ci#include <linux/seq_file.h>
4562306a36Sopenharmony_ci#include <linux/slab.h>
4662306a36Sopenharmony_ci#include <linux/spinlock.h>
4762306a36Sopenharmony_ci#include <linux/io.h>
4862306a36Sopenharmony_ci#include <linux/uaccess.h>
4962306a36Sopenharmony_ci#include <linux/mutex.h>
5062306a36Sopenharmony_ci#include <linux/pagemap.h>
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci#ifdef CONFIG_PPC
5362306a36Sopenharmony_ci#include <asm/nvram.h>
5462306a36Sopenharmony_ci#endif
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic DEFINE_MUTEX(nvram_mutex);
5762306a36Sopenharmony_cistatic DEFINE_SPINLOCK(nvram_state_lock);
5862306a36Sopenharmony_cistatic int nvram_open_cnt;	/* #times opened */
5962306a36Sopenharmony_cistatic int nvram_open_mode;	/* special open modes */
6062306a36Sopenharmony_cistatic ssize_t nvram_size;
6162306a36Sopenharmony_ci#define NVRAM_WRITE		1 /* opened for writing (exclusive) */
6262306a36Sopenharmony_ci#define NVRAM_EXCL		2 /* opened with O_EXCL */
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci#ifdef CONFIG_X86
6562306a36Sopenharmony_ci/*
6662306a36Sopenharmony_ci * These functions are provided to be called internally or by other parts of
6762306a36Sopenharmony_ci * the kernel. It's up to the caller to ensure correct checksum before reading
6862306a36Sopenharmony_ci * or after writing (needs to be done only once).
6962306a36Sopenharmony_ci *
7062306a36Sopenharmony_ci * It is worth noting that these functions all access bytes of general
7162306a36Sopenharmony_ci * purpose memory in the NVRAM - that is to say, they all add the
7262306a36Sopenharmony_ci * NVRAM_FIRST_BYTE offset.  Pass them offsets into NVRAM as if you did not
7362306a36Sopenharmony_ci * know about the RTC cruft.
7462306a36Sopenharmony_ci */
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci#define NVRAM_BYTES		(128 - NVRAM_FIRST_BYTE)
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci/* Note that *all* calls to CMOS_READ and CMOS_WRITE must be done with
7962306a36Sopenharmony_ci * rtc_lock held. Due to the index-port/data-port design of the RTC, we
8062306a36Sopenharmony_ci * don't want two different things trying to get to it at once. (e.g. the
8162306a36Sopenharmony_ci * periodic 11 min sync from kernel/time/ntp.c vs. this driver.)
8262306a36Sopenharmony_ci */
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic unsigned char __nvram_read_byte(int i)
8562306a36Sopenharmony_ci{
8662306a36Sopenharmony_ci	return CMOS_READ(NVRAM_FIRST_BYTE + i);
8762306a36Sopenharmony_ci}
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_cistatic unsigned char pc_nvram_read_byte(int i)
9062306a36Sopenharmony_ci{
9162306a36Sopenharmony_ci	unsigned long flags;
9262306a36Sopenharmony_ci	unsigned char c;
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	spin_lock_irqsave(&rtc_lock, flags);
9562306a36Sopenharmony_ci	c = __nvram_read_byte(i);
9662306a36Sopenharmony_ci	spin_unlock_irqrestore(&rtc_lock, flags);
9762306a36Sopenharmony_ci	return c;
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci/* This races nicely with trying to read with checksum checking (nvram_read) */
10162306a36Sopenharmony_cistatic void __nvram_write_byte(unsigned char c, int i)
10262306a36Sopenharmony_ci{
10362306a36Sopenharmony_ci	CMOS_WRITE(c, NVRAM_FIRST_BYTE + i);
10462306a36Sopenharmony_ci}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_cistatic void pc_nvram_write_byte(unsigned char c, int i)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	unsigned long flags;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	spin_lock_irqsave(&rtc_lock, flags);
11162306a36Sopenharmony_ci	__nvram_write_byte(c, i);
11262306a36Sopenharmony_ci	spin_unlock_irqrestore(&rtc_lock, flags);
11362306a36Sopenharmony_ci}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci/* On PCs, the checksum is built only over bytes 2..31 */
11662306a36Sopenharmony_ci#define PC_CKS_RANGE_START	2
11762306a36Sopenharmony_ci#define PC_CKS_RANGE_END	31
11862306a36Sopenharmony_ci#define PC_CKS_LOC		32
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_cistatic int __nvram_check_checksum(void)
12162306a36Sopenharmony_ci{
12262306a36Sopenharmony_ci	int i;
12362306a36Sopenharmony_ci	unsigned short sum = 0;
12462306a36Sopenharmony_ci	unsigned short expect;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	for (i = PC_CKS_RANGE_START; i <= PC_CKS_RANGE_END; ++i)
12762306a36Sopenharmony_ci		sum += __nvram_read_byte(i);
12862306a36Sopenharmony_ci	expect = __nvram_read_byte(PC_CKS_LOC)<<8 |
12962306a36Sopenharmony_ci	    __nvram_read_byte(PC_CKS_LOC+1);
13062306a36Sopenharmony_ci	return (sum & 0xffff) == expect;
13162306a36Sopenharmony_ci}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_cistatic void __nvram_set_checksum(void)
13462306a36Sopenharmony_ci{
13562306a36Sopenharmony_ci	int i;
13662306a36Sopenharmony_ci	unsigned short sum = 0;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	for (i = PC_CKS_RANGE_START; i <= PC_CKS_RANGE_END; ++i)
13962306a36Sopenharmony_ci		sum += __nvram_read_byte(i);
14062306a36Sopenharmony_ci	__nvram_write_byte(sum >> 8, PC_CKS_LOC);
14162306a36Sopenharmony_ci	__nvram_write_byte(sum & 0xff, PC_CKS_LOC + 1);
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_cistatic long pc_nvram_set_checksum(void)
14562306a36Sopenharmony_ci{
14662306a36Sopenharmony_ci	spin_lock_irq(&rtc_lock);
14762306a36Sopenharmony_ci	__nvram_set_checksum();
14862306a36Sopenharmony_ci	spin_unlock_irq(&rtc_lock);
14962306a36Sopenharmony_ci	return 0;
15062306a36Sopenharmony_ci}
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_cistatic long pc_nvram_initialize(void)
15362306a36Sopenharmony_ci{
15462306a36Sopenharmony_ci	ssize_t i;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	spin_lock_irq(&rtc_lock);
15762306a36Sopenharmony_ci	for (i = 0; i < NVRAM_BYTES; ++i)
15862306a36Sopenharmony_ci		__nvram_write_byte(0, i);
15962306a36Sopenharmony_ci	__nvram_set_checksum();
16062306a36Sopenharmony_ci	spin_unlock_irq(&rtc_lock);
16162306a36Sopenharmony_ci	return 0;
16262306a36Sopenharmony_ci}
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_cistatic ssize_t pc_nvram_get_size(void)
16562306a36Sopenharmony_ci{
16662306a36Sopenharmony_ci	return NVRAM_BYTES;
16762306a36Sopenharmony_ci}
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_cistatic ssize_t pc_nvram_read(char *buf, size_t count, loff_t *ppos)
17062306a36Sopenharmony_ci{
17162306a36Sopenharmony_ci	char *p = buf;
17262306a36Sopenharmony_ci	loff_t i;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	spin_lock_irq(&rtc_lock);
17562306a36Sopenharmony_ci	if (!__nvram_check_checksum()) {
17662306a36Sopenharmony_ci		spin_unlock_irq(&rtc_lock);
17762306a36Sopenharmony_ci		return -EIO;
17862306a36Sopenharmony_ci	}
17962306a36Sopenharmony_ci	for (i = *ppos; count > 0 && i < NVRAM_BYTES; --count, ++i, ++p)
18062306a36Sopenharmony_ci		*p = __nvram_read_byte(i);
18162306a36Sopenharmony_ci	spin_unlock_irq(&rtc_lock);
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	*ppos = i;
18462306a36Sopenharmony_ci	return p - buf;
18562306a36Sopenharmony_ci}
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_cistatic ssize_t pc_nvram_write(char *buf, size_t count, loff_t *ppos)
18862306a36Sopenharmony_ci{
18962306a36Sopenharmony_ci	char *p = buf;
19062306a36Sopenharmony_ci	loff_t i;
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	spin_lock_irq(&rtc_lock);
19362306a36Sopenharmony_ci	if (!__nvram_check_checksum()) {
19462306a36Sopenharmony_ci		spin_unlock_irq(&rtc_lock);
19562306a36Sopenharmony_ci		return -EIO;
19662306a36Sopenharmony_ci	}
19762306a36Sopenharmony_ci	for (i = *ppos; count > 0 && i < NVRAM_BYTES; --count, ++i, ++p)
19862306a36Sopenharmony_ci		__nvram_write_byte(*p, i);
19962306a36Sopenharmony_ci	__nvram_set_checksum();
20062306a36Sopenharmony_ci	spin_unlock_irq(&rtc_lock);
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	*ppos = i;
20362306a36Sopenharmony_ci	return p - buf;
20462306a36Sopenharmony_ci}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ciconst struct nvram_ops arch_nvram_ops = {
20762306a36Sopenharmony_ci	.read           = pc_nvram_read,
20862306a36Sopenharmony_ci	.write          = pc_nvram_write,
20962306a36Sopenharmony_ci	.read_byte      = pc_nvram_read_byte,
21062306a36Sopenharmony_ci	.write_byte     = pc_nvram_write_byte,
21162306a36Sopenharmony_ci	.get_size       = pc_nvram_get_size,
21262306a36Sopenharmony_ci	.set_checksum   = pc_nvram_set_checksum,
21362306a36Sopenharmony_ci	.initialize     = pc_nvram_initialize,
21462306a36Sopenharmony_ci};
21562306a36Sopenharmony_ciEXPORT_SYMBOL(arch_nvram_ops);
21662306a36Sopenharmony_ci#endif /* CONFIG_X86 */
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci/*
21962306a36Sopenharmony_ci * The are the file operation function for user access to /dev/nvram
22062306a36Sopenharmony_ci */
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_cistatic loff_t nvram_misc_llseek(struct file *file, loff_t offset, int origin)
22362306a36Sopenharmony_ci{
22462306a36Sopenharmony_ci	return generic_file_llseek_size(file, offset, origin, MAX_LFS_FILESIZE,
22562306a36Sopenharmony_ci					nvram_size);
22662306a36Sopenharmony_ci}
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_cistatic ssize_t nvram_misc_read(struct file *file, char __user *buf,
22962306a36Sopenharmony_ci			       size_t count, loff_t *ppos)
23062306a36Sopenharmony_ci{
23162306a36Sopenharmony_ci	char *tmp;
23262306a36Sopenharmony_ci	ssize_t ret;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	if (*ppos >= nvram_size)
23662306a36Sopenharmony_ci		return 0;
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	count = min_t(size_t, count, nvram_size - *ppos);
23962306a36Sopenharmony_ci	count = min_t(size_t, count, PAGE_SIZE);
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	tmp = kmalloc(count, GFP_KERNEL);
24262306a36Sopenharmony_ci	if (!tmp)
24362306a36Sopenharmony_ci		return -ENOMEM;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	ret = nvram_read(tmp, count, ppos);
24662306a36Sopenharmony_ci	if (ret <= 0)
24762306a36Sopenharmony_ci		goto out;
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	if (copy_to_user(buf, tmp, ret)) {
25062306a36Sopenharmony_ci		*ppos -= ret;
25162306a36Sopenharmony_ci		ret = -EFAULT;
25262306a36Sopenharmony_ci	}
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ciout:
25562306a36Sopenharmony_ci	kfree(tmp);
25662306a36Sopenharmony_ci	return ret;
25762306a36Sopenharmony_ci}
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_cistatic ssize_t nvram_misc_write(struct file *file, const char __user *buf,
26062306a36Sopenharmony_ci				size_t count, loff_t *ppos)
26162306a36Sopenharmony_ci{
26262306a36Sopenharmony_ci	char *tmp;
26362306a36Sopenharmony_ci	ssize_t ret;
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	if (*ppos >= nvram_size)
26662306a36Sopenharmony_ci		return 0;
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	count = min_t(size_t, count, nvram_size - *ppos);
26962306a36Sopenharmony_ci	count = min_t(size_t, count, PAGE_SIZE);
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	tmp = memdup_user(buf, count);
27262306a36Sopenharmony_ci	if (IS_ERR(tmp))
27362306a36Sopenharmony_ci		return PTR_ERR(tmp);
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	ret = nvram_write(tmp, count, ppos);
27662306a36Sopenharmony_ci	kfree(tmp);
27762306a36Sopenharmony_ci	return ret;
27862306a36Sopenharmony_ci}
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_cistatic long nvram_misc_ioctl(struct file *file, unsigned int cmd,
28162306a36Sopenharmony_ci			     unsigned long arg)
28262306a36Sopenharmony_ci{
28362306a36Sopenharmony_ci	long ret = -ENOTTY;
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	switch (cmd) {
28662306a36Sopenharmony_ci#ifdef CONFIG_PPC
28762306a36Sopenharmony_ci	case OBSOLETE_PMAC_NVRAM_GET_OFFSET:
28862306a36Sopenharmony_ci		pr_warn("nvram: Using obsolete PMAC_NVRAM_GET_OFFSET ioctl\n");
28962306a36Sopenharmony_ci		fallthrough;
29062306a36Sopenharmony_ci	case IOC_NVRAM_GET_OFFSET:
29162306a36Sopenharmony_ci		ret = -EINVAL;
29262306a36Sopenharmony_ci#ifdef CONFIG_PPC_PMAC
29362306a36Sopenharmony_ci		if (machine_is(powermac)) {
29462306a36Sopenharmony_ci			int part, offset;
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci			if (copy_from_user(&part, (void __user *)arg,
29762306a36Sopenharmony_ci					   sizeof(part)) != 0)
29862306a36Sopenharmony_ci				return -EFAULT;
29962306a36Sopenharmony_ci			if (part < pmac_nvram_OF || part > pmac_nvram_NR)
30062306a36Sopenharmony_ci				return -EINVAL;
30162306a36Sopenharmony_ci			offset = pmac_get_partition(part);
30262306a36Sopenharmony_ci			if (offset < 0)
30362306a36Sopenharmony_ci				return -EINVAL;
30462306a36Sopenharmony_ci			if (copy_to_user((void __user *)arg,
30562306a36Sopenharmony_ci					 &offset, sizeof(offset)) != 0)
30662306a36Sopenharmony_ci				return -EFAULT;
30762306a36Sopenharmony_ci			ret = 0;
30862306a36Sopenharmony_ci		}
30962306a36Sopenharmony_ci#endif
31062306a36Sopenharmony_ci		break;
31162306a36Sopenharmony_ci#ifdef CONFIG_PPC32
31262306a36Sopenharmony_ci	case IOC_NVRAM_SYNC:
31362306a36Sopenharmony_ci		if (ppc_md.nvram_sync != NULL) {
31462306a36Sopenharmony_ci			mutex_lock(&nvram_mutex);
31562306a36Sopenharmony_ci			ppc_md.nvram_sync();
31662306a36Sopenharmony_ci			mutex_unlock(&nvram_mutex);
31762306a36Sopenharmony_ci		}
31862306a36Sopenharmony_ci		ret = 0;
31962306a36Sopenharmony_ci		break;
32062306a36Sopenharmony_ci#endif
32162306a36Sopenharmony_ci#elif defined(CONFIG_X86) || defined(CONFIG_M68K)
32262306a36Sopenharmony_ci	case NVRAM_INIT:
32362306a36Sopenharmony_ci		/* initialize NVRAM contents and checksum */
32462306a36Sopenharmony_ci		if (!capable(CAP_SYS_ADMIN))
32562306a36Sopenharmony_ci			return -EACCES;
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci		if (arch_nvram_ops.initialize != NULL) {
32862306a36Sopenharmony_ci			mutex_lock(&nvram_mutex);
32962306a36Sopenharmony_ci			ret = arch_nvram_ops.initialize();
33062306a36Sopenharmony_ci			mutex_unlock(&nvram_mutex);
33162306a36Sopenharmony_ci		}
33262306a36Sopenharmony_ci		break;
33362306a36Sopenharmony_ci	case NVRAM_SETCKS:
33462306a36Sopenharmony_ci		/* just set checksum, contents unchanged (maybe useful after
33562306a36Sopenharmony_ci		 * checksum garbaged somehow...) */
33662306a36Sopenharmony_ci		if (!capable(CAP_SYS_ADMIN))
33762306a36Sopenharmony_ci			return -EACCES;
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci		if (arch_nvram_ops.set_checksum != NULL) {
34062306a36Sopenharmony_ci			mutex_lock(&nvram_mutex);
34162306a36Sopenharmony_ci			ret = arch_nvram_ops.set_checksum();
34262306a36Sopenharmony_ci			mutex_unlock(&nvram_mutex);
34362306a36Sopenharmony_ci		}
34462306a36Sopenharmony_ci		break;
34562306a36Sopenharmony_ci#endif /* CONFIG_X86 || CONFIG_M68K */
34662306a36Sopenharmony_ci	}
34762306a36Sopenharmony_ci	return ret;
34862306a36Sopenharmony_ci}
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_cistatic int nvram_misc_open(struct inode *inode, struct file *file)
35162306a36Sopenharmony_ci{
35262306a36Sopenharmony_ci	spin_lock(&nvram_state_lock);
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci	/* Prevent multiple readers/writers if desired. */
35562306a36Sopenharmony_ci	if ((nvram_open_cnt && (file->f_flags & O_EXCL)) ||
35662306a36Sopenharmony_ci	    (nvram_open_mode & NVRAM_EXCL)) {
35762306a36Sopenharmony_ci		spin_unlock(&nvram_state_lock);
35862306a36Sopenharmony_ci		return -EBUSY;
35962306a36Sopenharmony_ci	}
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci#if defined(CONFIG_X86) || defined(CONFIG_M68K)
36262306a36Sopenharmony_ci	/* Prevent multiple writers if the set_checksum ioctl is implemented. */
36362306a36Sopenharmony_ci	if ((arch_nvram_ops.set_checksum != NULL) &&
36462306a36Sopenharmony_ci	    (file->f_mode & FMODE_WRITE) && (nvram_open_mode & NVRAM_WRITE)) {
36562306a36Sopenharmony_ci		spin_unlock(&nvram_state_lock);
36662306a36Sopenharmony_ci		return -EBUSY;
36762306a36Sopenharmony_ci	}
36862306a36Sopenharmony_ci#endif
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci	if (file->f_flags & O_EXCL)
37162306a36Sopenharmony_ci		nvram_open_mode |= NVRAM_EXCL;
37262306a36Sopenharmony_ci	if (file->f_mode & FMODE_WRITE)
37362306a36Sopenharmony_ci		nvram_open_mode |= NVRAM_WRITE;
37462306a36Sopenharmony_ci	nvram_open_cnt++;
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_ci	spin_unlock(&nvram_state_lock);
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci	return 0;
37962306a36Sopenharmony_ci}
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_cistatic int nvram_misc_release(struct inode *inode, struct file *file)
38262306a36Sopenharmony_ci{
38362306a36Sopenharmony_ci	spin_lock(&nvram_state_lock);
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_ci	nvram_open_cnt--;
38662306a36Sopenharmony_ci
38762306a36Sopenharmony_ci	/* if only one instance is open, clear the EXCL bit */
38862306a36Sopenharmony_ci	if (nvram_open_mode & NVRAM_EXCL)
38962306a36Sopenharmony_ci		nvram_open_mode &= ~NVRAM_EXCL;
39062306a36Sopenharmony_ci	if (file->f_mode & FMODE_WRITE)
39162306a36Sopenharmony_ci		nvram_open_mode &= ~NVRAM_WRITE;
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci	spin_unlock(&nvram_state_lock);
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci	return 0;
39662306a36Sopenharmony_ci}
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_ci#if defined(CONFIG_X86) && defined(CONFIG_PROC_FS)
39962306a36Sopenharmony_cistatic const char * const floppy_types[] = {
40062306a36Sopenharmony_ci	"none", "5.25'' 360k", "5.25'' 1.2M", "3.5'' 720k", "3.5'' 1.44M",
40162306a36Sopenharmony_ci	"3.5'' 2.88M", "3.5'' 2.88M"
40262306a36Sopenharmony_ci};
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_cistatic const char * const gfx_types[] = {
40562306a36Sopenharmony_ci	"EGA, VGA, ... (with BIOS)",
40662306a36Sopenharmony_ci	"CGA (40 cols)",
40762306a36Sopenharmony_ci	"CGA (80 cols)",
40862306a36Sopenharmony_ci	"monochrome",
40962306a36Sopenharmony_ci};
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_cistatic void pc_nvram_proc_read(unsigned char *nvram, struct seq_file *seq,
41262306a36Sopenharmony_ci			       void *offset)
41362306a36Sopenharmony_ci{
41462306a36Sopenharmony_ci	int checksum;
41562306a36Sopenharmony_ci	int type;
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_ci	spin_lock_irq(&rtc_lock);
41862306a36Sopenharmony_ci	checksum = __nvram_check_checksum();
41962306a36Sopenharmony_ci	spin_unlock_irq(&rtc_lock);
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci	seq_printf(seq, "Checksum status: %svalid\n", checksum ? "" : "not ");
42262306a36Sopenharmony_ci
42362306a36Sopenharmony_ci	seq_printf(seq, "# floppies     : %d\n",
42462306a36Sopenharmony_ci	    (nvram[6] & 1) ? (nvram[6] >> 6) + 1 : 0);
42562306a36Sopenharmony_ci	seq_printf(seq, "Floppy 0 type  : ");
42662306a36Sopenharmony_ci	type = nvram[2] >> 4;
42762306a36Sopenharmony_ci	if (type < ARRAY_SIZE(floppy_types))
42862306a36Sopenharmony_ci		seq_printf(seq, "%s\n", floppy_types[type]);
42962306a36Sopenharmony_ci	else
43062306a36Sopenharmony_ci		seq_printf(seq, "%d (unknown)\n", type);
43162306a36Sopenharmony_ci	seq_printf(seq, "Floppy 1 type  : ");
43262306a36Sopenharmony_ci	type = nvram[2] & 0x0f;
43362306a36Sopenharmony_ci	if (type < ARRAY_SIZE(floppy_types))
43462306a36Sopenharmony_ci		seq_printf(seq, "%s\n", floppy_types[type]);
43562306a36Sopenharmony_ci	else
43662306a36Sopenharmony_ci		seq_printf(seq, "%d (unknown)\n", type);
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_ci	seq_printf(seq, "HD 0 type      : ");
43962306a36Sopenharmony_ci	type = nvram[4] >> 4;
44062306a36Sopenharmony_ci	if (type)
44162306a36Sopenharmony_ci		seq_printf(seq, "%02x\n", type == 0x0f ? nvram[11] : type);
44262306a36Sopenharmony_ci	else
44362306a36Sopenharmony_ci		seq_printf(seq, "none\n");
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci	seq_printf(seq, "HD 1 type      : ");
44662306a36Sopenharmony_ci	type = nvram[4] & 0x0f;
44762306a36Sopenharmony_ci	if (type)
44862306a36Sopenharmony_ci		seq_printf(seq, "%02x\n", type == 0x0f ? nvram[12] : type);
44962306a36Sopenharmony_ci	else
45062306a36Sopenharmony_ci		seq_printf(seq, "none\n");
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_ci	seq_printf(seq, "HD type 48 data: %d/%d/%d C/H/S, precomp %d, lz %d\n",
45362306a36Sopenharmony_ci	    nvram[18] | (nvram[19] << 8),
45462306a36Sopenharmony_ci	    nvram[20], nvram[25],
45562306a36Sopenharmony_ci	    nvram[21] | (nvram[22] << 8), nvram[23] | (nvram[24] << 8));
45662306a36Sopenharmony_ci	seq_printf(seq, "HD type 49 data: %d/%d/%d C/H/S, precomp %d, lz %d\n",
45762306a36Sopenharmony_ci	    nvram[39] | (nvram[40] << 8),
45862306a36Sopenharmony_ci	    nvram[41], nvram[46],
45962306a36Sopenharmony_ci	    nvram[42] | (nvram[43] << 8), nvram[44] | (nvram[45] << 8));
46062306a36Sopenharmony_ci
46162306a36Sopenharmony_ci	seq_printf(seq, "DOS base memory: %d kB\n", nvram[7] | (nvram[8] << 8));
46262306a36Sopenharmony_ci	seq_printf(seq, "Extended memory: %d kB (configured), %d kB (tested)\n",
46362306a36Sopenharmony_ci	    nvram[9] | (nvram[10] << 8), nvram[34] | (nvram[35] << 8));
46462306a36Sopenharmony_ci
46562306a36Sopenharmony_ci	seq_printf(seq, "Gfx adapter    : %s\n",
46662306a36Sopenharmony_ci	    gfx_types[(nvram[6] >> 4) & 3]);
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	seq_printf(seq, "FPU            : %sinstalled\n",
46962306a36Sopenharmony_ci	    (nvram[6] & 2) ? "" : "not ");
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_ci	return;
47262306a36Sopenharmony_ci}
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_cistatic int nvram_proc_read(struct seq_file *seq, void *offset)
47562306a36Sopenharmony_ci{
47662306a36Sopenharmony_ci	unsigned char contents[NVRAM_BYTES];
47762306a36Sopenharmony_ci	int i = 0;
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_ci	spin_lock_irq(&rtc_lock);
48062306a36Sopenharmony_ci	for (i = 0; i < NVRAM_BYTES; ++i)
48162306a36Sopenharmony_ci		contents[i] = __nvram_read_byte(i);
48262306a36Sopenharmony_ci	spin_unlock_irq(&rtc_lock);
48362306a36Sopenharmony_ci
48462306a36Sopenharmony_ci	pc_nvram_proc_read(contents, seq, offset);
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_ci	return 0;
48762306a36Sopenharmony_ci}
48862306a36Sopenharmony_ci#endif /* CONFIG_X86 && CONFIG_PROC_FS */
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_cistatic const struct file_operations nvram_misc_fops = {
49162306a36Sopenharmony_ci	.owner		= THIS_MODULE,
49262306a36Sopenharmony_ci	.llseek		= nvram_misc_llseek,
49362306a36Sopenharmony_ci	.read		= nvram_misc_read,
49462306a36Sopenharmony_ci	.write		= nvram_misc_write,
49562306a36Sopenharmony_ci	.unlocked_ioctl	= nvram_misc_ioctl,
49662306a36Sopenharmony_ci	.open		= nvram_misc_open,
49762306a36Sopenharmony_ci	.release	= nvram_misc_release,
49862306a36Sopenharmony_ci};
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_cistatic struct miscdevice nvram_misc = {
50162306a36Sopenharmony_ci	NVRAM_MINOR,
50262306a36Sopenharmony_ci	"nvram",
50362306a36Sopenharmony_ci	&nvram_misc_fops,
50462306a36Sopenharmony_ci};
50562306a36Sopenharmony_ci
50662306a36Sopenharmony_cistatic int __init nvram_module_init(void)
50762306a36Sopenharmony_ci{
50862306a36Sopenharmony_ci	int ret;
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_ci	nvram_size = nvram_get_size();
51162306a36Sopenharmony_ci	if (nvram_size < 0)
51262306a36Sopenharmony_ci		return nvram_size;
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_ci	ret = misc_register(&nvram_misc);
51562306a36Sopenharmony_ci	if (ret) {
51662306a36Sopenharmony_ci		pr_err("nvram: can't misc_register on minor=%d\n", NVRAM_MINOR);
51762306a36Sopenharmony_ci		return ret;
51862306a36Sopenharmony_ci	}
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_ci#if defined(CONFIG_X86) && defined(CONFIG_PROC_FS)
52162306a36Sopenharmony_ci	if (!proc_create_single("driver/nvram", 0, NULL, nvram_proc_read)) {
52262306a36Sopenharmony_ci		pr_err("nvram: can't create /proc/driver/nvram\n");
52362306a36Sopenharmony_ci		misc_deregister(&nvram_misc);
52462306a36Sopenharmony_ci		return -ENOMEM;
52562306a36Sopenharmony_ci	}
52662306a36Sopenharmony_ci#endif
52762306a36Sopenharmony_ci
52862306a36Sopenharmony_ci	pr_info("Non-volatile memory driver v" NVRAM_VERSION "\n");
52962306a36Sopenharmony_ci	return 0;
53062306a36Sopenharmony_ci}
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_cistatic void __exit nvram_module_exit(void)
53362306a36Sopenharmony_ci{
53462306a36Sopenharmony_ci#if defined(CONFIG_X86) && defined(CONFIG_PROC_FS)
53562306a36Sopenharmony_ci	remove_proc_entry("driver/nvram", NULL);
53662306a36Sopenharmony_ci#endif
53762306a36Sopenharmony_ci	misc_deregister(&nvram_misc);
53862306a36Sopenharmony_ci}
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_cimodule_init(nvram_module_init);
54162306a36Sopenharmony_cimodule_exit(nvram_module_exit);
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_ciMODULE_LICENSE("GPL");
54462306a36Sopenharmony_ciMODULE_ALIAS_MISCDEV(NVRAM_MINOR);
54562306a36Sopenharmony_ciMODULE_ALIAS("devname:nvram");
546