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