/* * OpenBIOS - free your system! * ( firmware/flash device driver for Linux ) * * filesystem.c - vfs character device interface * * This program is part of a free implementation of the IEEE 1275-1994 * Standard for Boot (Initialization Configuration) Firmware. * * Copyright (C) 1998-2004 Stefan Reinauer, <stepan@openbios.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA, 02110-1301 USA * */ #include <linux/config.h> #include <linux/version.h> #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) && defined(MODVERSIONS) #include <linux/modversions.h> #endif #include <linux/module.h> #include <linux/errno.h> #include <linux/types.h> #include <linux/vmalloc.h> #include <linux/fcntl.h> #include <linux/delay.h> #include <asm/uaccess.h> #include "bios.h" #include "flashchips.h" #include "pcisets.h" #include "programming.h" #ifdef MODULE void inc_mod(void); void dec_mod(void); #endif /* * ****************************************** * * /dev/bios filesystem operations * * ****************************************** */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) #define FDEV (MINOR(file->f_dentry->d_inode->i_rdev)) #else #define FDEV (iminor(file->f_dentry->d_inode)) #endif #define CFLASH flashdevices[FDEV] // #define BIOS_SIZE ((flashchips[CFLASH.flashnum].size)*1024) #define BIOS_SIZE (CFLASH.size) static loff_t bios_llseek(struct file *file, loff_t offset, int origin ) { currflash=FDEV; switch(origin) { case 0: break; case 1: offset += file->f_pos; break; case 2: offset += BIOS_SIZE; break; } return((offset >= 0)?(file->f_pos = offset):-EINVAL); } static ssize_t bios_read(struct file *file, char *buffer, size_t count, loff_t *ppos) { signed int size=((BIOS_SIZE-*ppos>count) ? count : BIOS_SIZE-*ppos); unsigned char *addr = (unsigned char*)CFLASH.mapped + CFLASH.offset; int i; currflash = FDEV; devices[flashdevices[currflash].idx].activate(); for (i=0;i<size;i++) buffer[i]=flash_readb(addr,*ppos+i); devices[flashdevices[currflash].idx].deactivate(); *ppos+=size; return size; } static ssize_t bios_write(struct file *file, const char *buffer, size_t count, loff_t *ppos) { unsigned long flags; unsigned int offset=0, startsec=0, endsec=0; unsigned int secnum=0, size=0, writeoffs=0; unsigned int i, fn; unsigned char *clipboard; unsigned char *addr = (unsigned char*)CFLASH.mapped + CFLASH.offset; currflash=FDEV; fn=CFLASH.flashnum; /* Some security checks. */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) if (!suser()) return -EACCES; #endif if (!write) { printk (KERN_WARNING "Writing is disabled for security reasons. RTFM.\n"); return -EACCES; } if (!flashchips[fn].supported) { printk (KERN_ERR "BIOS: Flash device not supported.\n"); return -EMEDIUMTYPE; } if ( count > BIOS_SIZE-*ppos ) return -EFBIG; /* FIXME: Autoselect(AMD) BC-90 * -> 00/MID; * 01/PID; * 02/Protected (1=yes/0=no) */ /* Determine size of data to be written */ if (!(flashchips[fn].flags & f_needs_erase) ) { offset=(unsigned int)*ppos&~(flashchips[fn].pagesize-1); size=(((unsigned int)*ppos+count+(flashchips[fn].pagesize-1))& ~(flashchips[CFLASH.flashnum].pagesize-1))-offset; } else { while (flashchips[fn].sectors[secnum] <= flashchips[fn].size ) { if ((unsigned int)*ppos >= flashchips[fn].sectors[secnum]*1024) { offset=flashchips[fn].sectors[secnum]*1024; startsec=secnum; } if ((unsigned int)*ppos+count-1 <= flashchips[fn].sectors[secnum]*1024) { size=(flashchips[fn].sectors[secnum]*1024)-offset; endsec=secnum-1; break; } secnum++; } } #ifdef DEBUG printk (KERN_DEBUG "BIOS: Write [0x%06x..0x%06x] [0x%06x..0x%06x]\n", (unsigned int)(*ppos),(unsigned int)(*ppos+count-1),offset,offset+size-1); #endif /* prepare data for writing */ clipboard=vmalloc(size); spin_lock_irqsave(&bios_lock, flags); devices[flashdevices[currflash].idx].activate(); for (i=0; i < size; i++) clipboard[i] = flash_readb(addr,offset+i); copy_from_user(clipboard+(*ppos-offset), buffer, count); /* start write access */ if (flashchips[fn].flags & f_intel_compl) { iflash_erase_sectors(addr,fn,startsec,endsec); for (i=0;i<size;i++) iflash_program_byte(addr, offset+i, clipboard[i]); flash_command(addr, 0xff); } else { if (flashchips[fn].flags & f_needs_erase) { if (size == flashchips[fn].size*1024) { /* whole chip erase */ printk (KERN_DEBUG "BIOS: Erasing via whole chip method\n"); flash_erase(addr, fn); } else { printk (KERN_DEBUG "BIOS: Erasing via sector method\n"); flash_erase_sectors(addr, fn,startsec,endsec); } } while (size>0) { if ((flashchips[fn].flags & f_manuf_compl) != f_atmel_compl) { flash_program(addr); } else { flash_program_atmel(addr); } for (i=0;i<flashchips[fn].pagesize;i++) { flash_writeb(addr,offset+writeoffs+i,clipboard[writeoffs+i]); } if ((flashchips[fn].flags & f_manuf_compl) == f_atmel_compl) { udelay(750); } else { if (flashchips[fn].pagesize==1) udelay(30); else udelay(300); } if (flash_ready_poll(addr,offset+writeoffs+flashchips[fn].pagesize-1, clipboard[writeoffs+flashchips[fn].pagesize-1])) { printk (KERN_ERR "BIOS: Error occured, please repeat write operation.\n"); } flash_command(addr, 0xf0); writeoffs += flashchips[fn].pagesize; size -= flashchips[fn].pagesize; } } devices[flashdevices[currflash].idx].deactivate(); spin_unlock_irqrestore(&bios_lock, flags); vfree(clipboard); *ppos+=count; return count; } static int bios_open(struct inode *inode, struct file *file) { currflash=FDEV; if (flashcount<=FDEV) { printk (KERN_ERR "BIOS: There is no device (%d).\n",FDEV); return -ENODEV; } #ifdef DEBUG printk(KERN_DEBUG "BIOS: Opening device %d\n",FDEV); #endif /* Only one shall open for writing */ if ((CFLASH.open_cnt && (file->f_flags & O_EXCL)) || (CFLASH.open_mode & O_EXCL) || ((file->f_mode & 2) && (CFLASH.open_mode & O_RDWR))) return -EBUSY; if (file->f_flags & O_EXCL) CFLASH.open_mode |= O_EXCL; if (file->f_mode & 2) CFLASH.open_mode |= O_RDWR; CFLASH.open_cnt++; #ifdef MODULE inc_mod(); #endif return 0; } static int bios_release(struct inode *inode, struct file *file) { currflash=FDEV; if (file->f_flags & O_EXCL) CFLASH.open_mode &= ~O_EXCL; if (file->f_mode & 2) CFLASH.open_mode &= ~O_RDWR; CFLASH.open_cnt--; #ifdef MODULE dec_mod(); #endif return 0; } struct file_operations bios_fops = { .owner = THIS_MODULE, .llseek = bios_llseek, .read = bios_read, .write = bios_write, .open = bios_open, .release = bios_release, };