patch-2.3.47 linux/arch/i386/kernel/microcode.c

Next file: linux/arch/i386/kernel/mtrr.c
Previous file: linux/arch/i386/kernel/irq.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.46/linux/arch/i386/kernel/microcode.c linux/arch/i386/kernel/microcode.c
@@ -18,6 +18,8 @@
  *
  *	1.0	16 February 2000, Tigran Aivazian <tigran@sco.com>
  *		Initial release.
+ *	1.01	18 February 2000, Tigran Aivazian <tigran@sco.com>
+ *		Added read() support + cleanups.
  */
 
 #include <linux/init.h>
@@ -31,7 +33,7 @@
 #include <asm/uaccess.h>
 #include <asm/processor.h>
 
-#define MICROCODE_VERSION 	"1.0"
+#define MICROCODE_VERSION 	"1.01"
 
 MODULE_DESCRIPTION("CPU (P6) microcode update driver");
 MODULE_AUTHOR("Tigran Aivazian <tigran@ocston.org>");
@@ -40,11 +42,12 @@
 /* VFS interface */
 static int microcode_open(struct inode *, struct file *);
 static int microcode_release(struct inode *, struct file *);
+static ssize_t microcode_read(struct file *, char *, size_t, loff_t *);
 static ssize_t microcode_write(struct file *, const char *, size_t, loff_t *);
 
 
 /* internal helpers to do the work */
-static void do_microcode_update(void);
+static int do_microcode_update(void);
 static void do_update_one(void *);
 
 /*
@@ -56,8 +59,10 @@
 /* the actual array of microcode blocks, each 2048 bytes */
 static struct microcode * microcode = NULL;
 static unsigned int microcode_num = 0;
+static char *mc_applied = NULL; /* holds an array of applied microcode blocks */
 
 static struct file_operations microcode_fops = {
+	read:		microcode_read,
 	write:		microcode_write,
 	open:		microcode_open,
 	release:	microcode_release,
@@ -71,21 +76,32 @@
 
 static int __init microcode_init(void)
 {
-	/* write-only /proc/driver/microcode file, one day may become read-write.. */
-	proc_microcode = create_proc_entry("microcode", S_IWUSR, proc_root_driver);
+	int size;
+
+	proc_microcode = create_proc_entry("microcode", S_IWUSR|S_IRUSR, proc_root_driver);
 	if (!proc_microcode) {
-		printk(KERN_ERR "microcode: can't create /proc/driver/microcode entry\n");
+		printk(KERN_ERR "microcode: can't create /proc/driver/microcode\n");
 		return -ENOMEM;
 	}
 	proc_microcode->ops = &microcode_inops;
-	printk(KERN_ERR "P6 Microcode Update Driver v%s registered\n", MICROCODE_VERSION);
+	size = smp_num_cpus * sizeof(struct microcode);
+	mc_applied = kmalloc(size, GFP_KERNEL);
+	if (!mc_applied) {
+		remove_proc_entry("microcode", proc_root_driver);
+		printk(KERN_ERR "microcode: can't allocate memory for saved microcode\n");
+		return -ENOMEM;
+	}
+	memset(mc_applied, 0, size); /* so that reading from offsets corresponding to failed
+	                                update makes this obvious */
+	printk(KERN_INFO "P6 Microcode Update Driver v%s registered\n", MICROCODE_VERSION);
 	return 0;
 }
 
 static void __exit microcode_exit(void)
 {
 	remove_proc_entry("microcode", proc_root_driver);
-	printk(KERN_ERR "P6 Microcode Update Driver v%s unregistered\n", MICROCODE_VERSION);
+	kfree(mc_applied);
+	printk(KERN_INFO "P6 Microcode Update Driver v%s unregistered\n", MICROCODE_VERSION);
 }
 
 module_init(microcode_init);
@@ -116,23 +132,46 @@
 	return 0;
 }
 
+/* a pointer to 'struct update_req' is passed to the IPI hanlder = do_update_one()
+ * update_req[cpu].err is set to 1 if update failed on 'cpu', 0 otherwise
+ * if err==0, microcode[update_req[cpu].slot] points to applied block of microcode
+ */
+struct update_req {
+	int err;
+	int slot;
+} update_req[NR_CPUS];
 
-static void do_microcode_update(void)
+static int do_microcode_update(void)
 {
-	if (smp_call_function(do_update_one, NULL, 1, 0) != 0)
+	int i, error = 0, err;
+	struct microcode *m;
+
+	if (smp_call_function(do_update_one, (void *)update_req, 1, 1) != 0)
 		panic("do_microcode_update(): timed out waiting for other CPUs\n");
-	do_update_one(NULL);
+	do_update_one((void *)update_req);
+
+	for (i=0; i<smp_num_cpus; i++) {
+		err = update_req[i].err;
+		error += err;
+		if (!err) {
+			m = (struct microcode *)mc_applied + i;
+			memcpy(m, &microcode[update_req[i].slot], sizeof(struct microcode));
+		}
+	}
+	return error ? -EIO : 0;
 }
 
-static void do_update_one(void *unused)
+static void do_update_one(void *arg)
 {
+	struct update_req *req;
 	struct cpuinfo_x86 * c;
 	unsigned int pf = 0, val[2], rev, sig;
-	int i, id;
-
-	id = smp_processor_id();
-	c = cpu_data + id;
+	int i, cpu_num;
 
+	cpu_num = smp_processor_id();
+	c = cpu_data + cpu_num;
+	req = (struct update_req *)arg + cpu_num;
+	req->err = 1; /* be pessimistic */
 
 	if (c->x86_vendor != X86_VENDOR_INTEL || c->x86 < 6)
 		return;
@@ -153,7 +192,7 @@
 			if (microcode[i].rev <= rev) {
 				printk(KERN_ERR 
 					"microcode: CPU%d not 'upgrading' to earlier revision"
-					" %d (current=%d)\n", id, microcode[i].rev, rev);
+					" %d (current=%d)\n", cpu_num, microcode[i].rev, rev);
 			} else { 
 				int sum = 0;
 				struct microcode *m = &microcode[i];
@@ -163,29 +202,47 @@
 					sum += *sump;
 				if (sum != 0) {
 					printk(KERN_ERR "microcode: CPU%d aborting, "
-							"bad checksum\n", id);
+							"bad checksum\n", cpu_num);
 					break;
 				}
+
 				wrmsr(0x79, (unsigned int)(m->bits), 0);
 				__asm__ __volatile__ ("cpuid");
 				rdmsr(0x8B, val[0], val[1]);
+
+				req->err = 0;
+				req->slot = i;
 				printk(KERN_ERR "microcode: CPU%d microcode updated "
-						"from revision %d to %d\n", id, rev, val[1]);
+						"from revision %d to %d, date=%08x\n", 
+						cpu_num, rev, val[1], m->date);
 			}
 			break;
 		}
 }
 
+static ssize_t microcode_read(struct file *file, char *buf, size_t len, loff_t *ppos)
+{
+	size_t fsize = smp_num_cpus * sizeof(struct microcode);
+
+	if (!proc_microcode->size || *ppos >= fsize)
+		return 0; /* EOF */
+	if (*ppos + len > fsize)
+		len = fsize - *ppos;
+	if (copy_to_user(buf, mc_applied + *ppos, len))
+		return -EFAULT;
+	*ppos += len;
+	return len;
+}
+
 static ssize_t microcode_write(struct file *file, const char *buf, size_t len, loff_t *ppos)
 {
+	ssize_t ret;
+
 	if (len % sizeof(struct microcode) != 0) {
 		printk(KERN_ERR "microcode: can only write in N*%d bytes units\n", 
 			sizeof(struct microcode));
 		return -EINVAL;
-		return -EINVAL;
 	}
-	if (!capable(CAP_SYS_RAWIO))
-		return -EPERM;
 	lock_kernel();
 	microcode_num = len/sizeof(struct microcode);
 	microcode = vmalloc(len);
@@ -198,8 +255,12 @@
 		unlock_kernel();
 		return -EFAULT;
 	}
-	do_microcode_update();
+	ret = do_microcode_update();
+	if (!ret) {
+		proc_microcode->size = smp_num_cpus * sizeof(struct microcode);
+		ret = (ssize_t)len;
+	}
 	vfree(microcode);
 	unlock_kernel();
-	return len;
+	return ret;
 }

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)