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