xref: /openbmc/linux/arch/x86/boot/edd.c (revision 75bf465f0bc33e9b776a46d6a1b9b990f5fb7c37)
1*97873a3dSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
296ae6ea0SThomas Gleixner /* -*- linux-c -*- ------------------------------------------------------- *
396ae6ea0SThomas Gleixner  *
496ae6ea0SThomas Gleixner  *   Copyright (C) 1991, 1992 Linus Torvalds
596ae6ea0SThomas Gleixner  *   Copyright 2007 rPath, Inc. - All Rights Reserved
63435d347SH. Peter Anvin  *   Copyright 2009 Intel Corporation; author H. Peter Anvin
796ae6ea0SThomas Gleixner  *
896ae6ea0SThomas Gleixner  * ----------------------------------------------------------------------- */
996ae6ea0SThomas Gleixner 
1096ae6ea0SThomas Gleixner /*
1196ae6ea0SThomas Gleixner  * Get EDD BIOS disk information
1296ae6ea0SThomas Gleixner  */
1396ae6ea0SThomas Gleixner 
1496ae6ea0SThomas Gleixner #include "boot.h"
1596ae6ea0SThomas Gleixner #include <linux/edd.h>
16c041b5adSVivek Goyal #include "string.h"
1796ae6ea0SThomas Gleixner 
1896ae6ea0SThomas Gleixner #if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE)
1996ae6ea0SThomas Gleixner 
2096ae6ea0SThomas Gleixner /*
2196ae6ea0SThomas Gleixner  * Read the MBR (first sector) from a specific device.
2296ae6ea0SThomas Gleixner  */
read_mbr(u8 devno,void * buf)2396ae6ea0SThomas Gleixner static int read_mbr(u8 devno, void *buf)
2496ae6ea0SThomas Gleixner {
253435d347SH. Peter Anvin 	struct biosregs ireg, oreg;
2696ae6ea0SThomas Gleixner 
273435d347SH. Peter Anvin 	initregs(&ireg);
283435d347SH. Peter Anvin 	ireg.ax = 0x0201;		/* Legacy Read, one sector */
293435d347SH. Peter Anvin 	ireg.cx = 0x0001;		/* Sector 0-0-1 */
303435d347SH. Peter Anvin 	ireg.dl = devno;
313435d347SH. Peter Anvin 	ireg.bx = (size_t)buf;
3296ae6ea0SThomas Gleixner 
333435d347SH. Peter Anvin 	intcall(0x13, &ireg, &oreg);
343435d347SH. Peter Anvin 
353435d347SH. Peter Anvin 	return -(oreg.eflags & X86_EFLAGS_CF); /* 0 or -1 */
3696ae6ea0SThomas Gleixner }
3796ae6ea0SThomas Gleixner 
read_mbr_sig(u8 devno,struct edd_info * ei,u32 * mbrsig)3896ae6ea0SThomas Gleixner static u32 read_mbr_sig(u8 devno, struct edd_info *ei, u32 *mbrsig)
3996ae6ea0SThomas Gleixner {
4096ae6ea0SThomas Gleixner 	int sector_size;
4196ae6ea0SThomas Gleixner 	char *mbrbuf_ptr, *mbrbuf_end;
4296ae6ea0SThomas Gleixner 	u32 buf_base, mbr_base;
4396ae6ea0SThomas Gleixner 	extern char _end[];
446cdcdb99SAndrey Borzenkov 	u16 mbr_magic;
4596ae6ea0SThomas Gleixner 
4696ae6ea0SThomas Gleixner 	sector_size = ei->params.bytes_per_sector;
4796ae6ea0SThomas Gleixner 	if (!sector_size)
4896ae6ea0SThomas Gleixner 		sector_size = 512; /* Best available guess */
4996ae6ea0SThomas Gleixner 
5096ae6ea0SThomas Gleixner 	/* Produce a naturally aligned buffer on the heap */
5196ae6ea0SThomas Gleixner 	buf_base = (ds() << 4) + (u32)&_end;
5296ae6ea0SThomas Gleixner 	mbr_base = (buf_base+sector_size-1) & ~(sector_size-1);
5396ae6ea0SThomas Gleixner 	mbrbuf_ptr = _end + (mbr_base-buf_base);
5496ae6ea0SThomas Gleixner 	mbrbuf_end = mbrbuf_ptr + sector_size;
5596ae6ea0SThomas Gleixner 
5696ae6ea0SThomas Gleixner 	/* Make sure we actually have space on the heap... */
5796ae6ea0SThomas Gleixner 	if (!(boot_params.hdr.loadflags & CAN_USE_HEAP))
5896ae6ea0SThomas Gleixner 		return -1;
5996ae6ea0SThomas Gleixner 	if (mbrbuf_end > (char *)(size_t)boot_params.hdr.heap_end_ptr)
6096ae6ea0SThomas Gleixner 		return -1;
6196ae6ea0SThomas Gleixner 
626cdcdb99SAndrey Borzenkov 	memset(mbrbuf_ptr, 0, sector_size);
6396ae6ea0SThomas Gleixner 	if (read_mbr(devno, mbrbuf_ptr))
6496ae6ea0SThomas Gleixner 		return -1;
6596ae6ea0SThomas Gleixner 
6696ae6ea0SThomas Gleixner 	*mbrsig = *(u32 *)&mbrbuf_ptr[EDD_MBR_SIG_OFFSET];
676cdcdb99SAndrey Borzenkov 	mbr_magic = *(u16 *)&mbrbuf_ptr[510];
686cdcdb99SAndrey Borzenkov 
696cdcdb99SAndrey Borzenkov 	/* check for valid MBR magic */
706cdcdb99SAndrey Borzenkov 	return mbr_magic == 0xAA55 ? 0 : -1;
7196ae6ea0SThomas Gleixner }
7296ae6ea0SThomas Gleixner 
get_edd_info(u8 devno,struct edd_info * ei)7396ae6ea0SThomas Gleixner static int get_edd_info(u8 devno, struct edd_info *ei)
7496ae6ea0SThomas Gleixner {
753435d347SH. Peter Anvin 	struct biosregs ireg, oreg;
7696ae6ea0SThomas Gleixner 
770e96f31eSJordan Borgner 	memset(ei, 0, sizeof(*ei));
7896ae6ea0SThomas Gleixner 
7996ae6ea0SThomas Gleixner 	/* Check Extensions Present */
8096ae6ea0SThomas Gleixner 
813435d347SH. Peter Anvin 	initregs(&ireg);
823435d347SH. Peter Anvin 	ireg.ah = 0x41;
833435d347SH. Peter Anvin 	ireg.bx = EDDMAGIC1;
843435d347SH. Peter Anvin 	ireg.dl = devno;
853435d347SH. Peter Anvin 	intcall(0x13, &ireg, &oreg);
8696ae6ea0SThomas Gleixner 
873435d347SH. Peter Anvin 	if (oreg.eflags & X86_EFLAGS_CF)
8896ae6ea0SThomas Gleixner 		return -1;	/* No extended information */
8996ae6ea0SThomas Gleixner 
903435d347SH. Peter Anvin 	if (oreg.bx != EDDMAGIC2)
9196ae6ea0SThomas Gleixner 		return -1;
9296ae6ea0SThomas Gleixner 
9396ae6ea0SThomas Gleixner 	ei->device  = devno;
943435d347SH. Peter Anvin 	ei->version = oreg.ah;		 /* EDD version number */
953435d347SH. Peter Anvin 	ei->interface_support = oreg.cx; /* EDD functionality subsets */
9696ae6ea0SThomas Gleixner 
9796ae6ea0SThomas Gleixner 	/* Extended Get Device Parameters */
9896ae6ea0SThomas Gleixner 
9996ae6ea0SThomas Gleixner 	ei->params.length = sizeof(ei->params);
1003435d347SH. Peter Anvin 	ireg.ah = 0x48;
1013435d347SH. Peter Anvin 	ireg.si = (size_t)&ei->params;
1023435d347SH. Peter Anvin 	intcall(0x13, &ireg, &oreg);
10396ae6ea0SThomas Gleixner 
10496ae6ea0SThomas Gleixner 	/* Get legacy CHS parameters */
10596ae6ea0SThomas Gleixner 
10696ae6ea0SThomas Gleixner 	/* Ralf Brown recommends setting ES:DI to 0:0 */
1073435d347SH. Peter Anvin 	ireg.ah = 0x08;
1083435d347SH. Peter Anvin 	ireg.es = 0;
1093435d347SH. Peter Anvin 	intcall(0x13, &ireg, &oreg);
11096ae6ea0SThomas Gleixner 
1113435d347SH. Peter Anvin 	if (!(oreg.eflags & X86_EFLAGS_CF)) {
1123435d347SH. Peter Anvin 		ei->legacy_max_cylinder = oreg.ch + ((oreg.cl & 0xc0) << 2);
1133435d347SH. Peter Anvin 		ei->legacy_max_head = oreg.dh;
1143435d347SH. Peter Anvin 		ei->legacy_sectors_per_track = oreg.cl & 0x3f;
11596ae6ea0SThomas Gleixner 	}
11696ae6ea0SThomas Gleixner 
11796ae6ea0SThomas Gleixner 	return 0;
11896ae6ea0SThomas Gleixner }
11996ae6ea0SThomas Gleixner 
query_edd(void)12096ae6ea0SThomas Gleixner void query_edd(void)
12196ae6ea0SThomas Gleixner {
12296ae6ea0SThomas Gleixner 	char eddarg[8];
12396ae6ea0SThomas Gleixner 	int do_mbr = 1;
1248c4dd606STim Gardner #ifdef CONFIG_EDD_OFF
1258c4dd606STim Gardner 	int do_edd = 0;
1268c4dd606STim Gardner #else
12796ae6ea0SThomas Gleixner 	int do_edd = 1;
1288c4dd606STim Gardner #endif
129e479c830Sdevzero@web.de 	int be_quiet;
13096ae6ea0SThomas Gleixner 	int devno;
13196ae6ea0SThomas Gleixner 	struct edd_info ei, *edp;
13296ae6ea0SThomas Gleixner 	u32 *mbrptr;
13396ae6ea0SThomas Gleixner 
1340e96f31eSJordan Borgner 	if (cmdline_find_option("edd", eddarg, sizeof(eddarg)) > 0) {
1358c4dd606STim Gardner 		if (!strcmp(eddarg, "skipmbr") || !strcmp(eddarg, "skip")) {
1368c4dd606STim Gardner 			do_edd = 1;
13796ae6ea0SThomas Gleixner 			do_mbr = 0;
1388c4dd606STim Gardner 		}
13996ae6ea0SThomas Gleixner 		else if (!strcmp(eddarg, "off"))
14096ae6ea0SThomas Gleixner 			do_edd = 0;
1418c4dd606STim Gardner 		else if (!strcmp(eddarg, "on"))
1428c4dd606STim Gardner 			do_edd = 1;
14396ae6ea0SThomas Gleixner 	}
14496ae6ea0SThomas Gleixner 
145e479c830Sdevzero@web.de 	be_quiet = cmdline_find_option_bool("quiet");
146e479c830Sdevzero@web.de 
14796ae6ea0SThomas Gleixner 	edp    = boot_params.eddbuf;
14896ae6ea0SThomas Gleixner 	mbrptr = boot_params.edd_mbr_sig_buffer;
14996ae6ea0SThomas Gleixner 
15096ae6ea0SThomas Gleixner 	if (!do_edd)
15196ae6ea0SThomas Gleixner 		return;
15296ae6ea0SThomas Gleixner 
153e479c830Sdevzero@web.de 	/* Bugs in OnBoard or AddOnCards Bios may hang the EDD probe,
154e479c830Sdevzero@web.de 	 * so give a hint if this happens.
155e479c830Sdevzero@web.de 	 */
156e479c830Sdevzero@web.de 
157e479c830Sdevzero@web.de 	if (!be_quiet)
158b7107636SH. Peter Anvin 		printf("Probing EDD (edd=off to disable)... ");
159e479c830Sdevzero@web.de 
16096ae6ea0SThomas Gleixner 	for (devno = 0x80; devno < 0x80+EDD_MBR_SIG_MAX; devno++) {
16196ae6ea0SThomas Gleixner 		/*
16296ae6ea0SThomas Gleixner 		 * Scan the BIOS-supported hard disks and query EDD
16396ae6ea0SThomas Gleixner 		 * information...
16496ae6ea0SThomas Gleixner 		 */
165f2ba9392SJan Beulich 		if (!get_edd_info(devno, &ei)
166f2ba9392SJan Beulich 		    && boot_params.eddbuf_entries < EDDMAXNR) {
1670e96f31eSJordan Borgner 			memcpy(edp, &ei, sizeof(ei));
16896ae6ea0SThomas Gleixner 			edp++;
16996ae6ea0SThomas Gleixner 			boot_params.eddbuf_entries++;
17096ae6ea0SThomas Gleixner 		}
17196ae6ea0SThomas Gleixner 
17296ae6ea0SThomas Gleixner 		if (do_mbr && !read_mbr_sig(devno, &ei, mbrptr++))
17396ae6ea0SThomas Gleixner 			boot_params.edd_mbr_sig_buf_entries = devno-0x80+1;
17496ae6ea0SThomas Gleixner 	}
175e479c830Sdevzero@web.de 
176e479c830Sdevzero@web.de 	if (!be_quiet)
177f7775016SH. Peter Anvin 		printf("ok\n");
17896ae6ea0SThomas Gleixner }
17996ae6ea0SThomas Gleixner 
18096ae6ea0SThomas Gleixner #endif
181