162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/* -*- linux-c -*- ------------------------------------------------------- *
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci *   Copyright (C) 1991, 1992 Linus Torvalds
562306a36Sopenharmony_ci *   Copyright 2007 rPath, Inc. - All Rights Reserved
662306a36Sopenharmony_ci *   Copyright 2009 Intel Corporation; author H. Peter Anvin
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * ----------------------------------------------------------------------- */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci/*
1162306a36Sopenharmony_ci * Get EDD BIOS disk information
1262306a36Sopenharmony_ci */
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include "boot.h"
1562306a36Sopenharmony_ci#include <linux/edd.h>
1662306a36Sopenharmony_ci#include "string.h"
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE)
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci/*
2162306a36Sopenharmony_ci * Read the MBR (first sector) from a specific device.
2262306a36Sopenharmony_ci */
2362306a36Sopenharmony_cistatic int read_mbr(u8 devno, void *buf)
2462306a36Sopenharmony_ci{
2562306a36Sopenharmony_ci	struct biosregs ireg, oreg;
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	initregs(&ireg);
2862306a36Sopenharmony_ci	ireg.ax = 0x0201;		/* Legacy Read, one sector */
2962306a36Sopenharmony_ci	ireg.cx = 0x0001;		/* Sector 0-0-1 */
3062306a36Sopenharmony_ci	ireg.dl = devno;
3162306a36Sopenharmony_ci	ireg.bx = (size_t)buf;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	intcall(0x13, &ireg, &oreg);
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci	return -(oreg.eflags & X86_EFLAGS_CF); /* 0 or -1 */
3662306a36Sopenharmony_ci}
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_cistatic u32 read_mbr_sig(u8 devno, struct edd_info *ei, u32 *mbrsig)
3962306a36Sopenharmony_ci{
4062306a36Sopenharmony_ci	int sector_size;
4162306a36Sopenharmony_ci	char *mbrbuf_ptr, *mbrbuf_end;
4262306a36Sopenharmony_ci	u32 buf_base, mbr_base;
4362306a36Sopenharmony_ci	extern char _end[];
4462306a36Sopenharmony_ci	u16 mbr_magic;
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	sector_size = ei->params.bytes_per_sector;
4762306a36Sopenharmony_ci	if (!sector_size)
4862306a36Sopenharmony_ci		sector_size = 512; /* Best available guess */
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	/* Produce a naturally aligned buffer on the heap */
5162306a36Sopenharmony_ci	buf_base = (ds() << 4) + (u32)&_end;
5262306a36Sopenharmony_ci	mbr_base = (buf_base+sector_size-1) & ~(sector_size-1);
5362306a36Sopenharmony_ci	mbrbuf_ptr = _end + (mbr_base-buf_base);
5462306a36Sopenharmony_ci	mbrbuf_end = mbrbuf_ptr + sector_size;
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	/* Make sure we actually have space on the heap... */
5762306a36Sopenharmony_ci	if (!(boot_params.hdr.loadflags & CAN_USE_HEAP))
5862306a36Sopenharmony_ci		return -1;
5962306a36Sopenharmony_ci	if (mbrbuf_end > (char *)(size_t)boot_params.hdr.heap_end_ptr)
6062306a36Sopenharmony_ci		return -1;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	memset(mbrbuf_ptr, 0, sector_size);
6362306a36Sopenharmony_ci	if (read_mbr(devno, mbrbuf_ptr))
6462306a36Sopenharmony_ci		return -1;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	*mbrsig = *(u32 *)&mbrbuf_ptr[EDD_MBR_SIG_OFFSET];
6762306a36Sopenharmony_ci	mbr_magic = *(u16 *)&mbrbuf_ptr[510];
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	/* check for valid MBR magic */
7062306a36Sopenharmony_ci	return mbr_magic == 0xAA55 ? 0 : -1;
7162306a36Sopenharmony_ci}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistatic int get_edd_info(u8 devno, struct edd_info *ei)
7462306a36Sopenharmony_ci{
7562306a36Sopenharmony_ci	struct biosregs ireg, oreg;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	memset(ei, 0, sizeof(*ei));
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	/* Check Extensions Present */
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	initregs(&ireg);
8262306a36Sopenharmony_ci	ireg.ah = 0x41;
8362306a36Sopenharmony_ci	ireg.bx = EDDMAGIC1;
8462306a36Sopenharmony_ci	ireg.dl = devno;
8562306a36Sopenharmony_ci	intcall(0x13, &ireg, &oreg);
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	if (oreg.eflags & X86_EFLAGS_CF)
8862306a36Sopenharmony_ci		return -1;	/* No extended information */
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	if (oreg.bx != EDDMAGIC2)
9162306a36Sopenharmony_ci		return -1;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	ei->device  = devno;
9462306a36Sopenharmony_ci	ei->version = oreg.ah;		 /* EDD version number */
9562306a36Sopenharmony_ci	ei->interface_support = oreg.cx; /* EDD functionality subsets */
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	/* Extended Get Device Parameters */
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	ei->params.length = sizeof(ei->params);
10062306a36Sopenharmony_ci	ireg.ah = 0x48;
10162306a36Sopenharmony_ci	ireg.si = (size_t)&ei->params;
10262306a36Sopenharmony_ci	intcall(0x13, &ireg, &oreg);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	/* Get legacy CHS parameters */
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	/* Ralf Brown recommends setting ES:DI to 0:0 */
10762306a36Sopenharmony_ci	ireg.ah = 0x08;
10862306a36Sopenharmony_ci	ireg.es = 0;
10962306a36Sopenharmony_ci	intcall(0x13, &ireg, &oreg);
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	if (!(oreg.eflags & X86_EFLAGS_CF)) {
11262306a36Sopenharmony_ci		ei->legacy_max_cylinder = oreg.ch + ((oreg.cl & 0xc0) << 2);
11362306a36Sopenharmony_ci		ei->legacy_max_head = oreg.dh;
11462306a36Sopenharmony_ci		ei->legacy_sectors_per_track = oreg.cl & 0x3f;
11562306a36Sopenharmony_ci	}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	return 0;
11862306a36Sopenharmony_ci}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_civoid query_edd(void)
12162306a36Sopenharmony_ci{
12262306a36Sopenharmony_ci	char eddarg[8];
12362306a36Sopenharmony_ci	int do_mbr = 1;
12462306a36Sopenharmony_ci#ifdef CONFIG_EDD_OFF
12562306a36Sopenharmony_ci	int do_edd = 0;
12662306a36Sopenharmony_ci#else
12762306a36Sopenharmony_ci	int do_edd = 1;
12862306a36Sopenharmony_ci#endif
12962306a36Sopenharmony_ci	int be_quiet;
13062306a36Sopenharmony_ci	int devno;
13162306a36Sopenharmony_ci	struct edd_info ei, *edp;
13262306a36Sopenharmony_ci	u32 *mbrptr;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	if (cmdline_find_option("edd", eddarg, sizeof(eddarg)) > 0) {
13562306a36Sopenharmony_ci		if (!strcmp(eddarg, "skipmbr") || !strcmp(eddarg, "skip")) {
13662306a36Sopenharmony_ci			do_edd = 1;
13762306a36Sopenharmony_ci			do_mbr = 0;
13862306a36Sopenharmony_ci		}
13962306a36Sopenharmony_ci		else if (!strcmp(eddarg, "off"))
14062306a36Sopenharmony_ci			do_edd = 0;
14162306a36Sopenharmony_ci		else if (!strcmp(eddarg, "on"))
14262306a36Sopenharmony_ci			do_edd = 1;
14362306a36Sopenharmony_ci	}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	be_quiet = cmdline_find_option_bool("quiet");
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	edp    = boot_params.eddbuf;
14862306a36Sopenharmony_ci	mbrptr = boot_params.edd_mbr_sig_buffer;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	if (!do_edd)
15162306a36Sopenharmony_ci		return;
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	/* Bugs in OnBoard or AddOnCards Bios may hang the EDD probe,
15462306a36Sopenharmony_ci	 * so give a hint if this happens.
15562306a36Sopenharmony_ci	 */
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	if (!be_quiet)
15862306a36Sopenharmony_ci		printf("Probing EDD (edd=off to disable)... ");
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	for (devno = 0x80; devno < 0x80+EDD_MBR_SIG_MAX; devno++) {
16162306a36Sopenharmony_ci		/*
16262306a36Sopenharmony_ci		 * Scan the BIOS-supported hard disks and query EDD
16362306a36Sopenharmony_ci		 * information...
16462306a36Sopenharmony_ci		 */
16562306a36Sopenharmony_ci		if (!get_edd_info(devno, &ei)
16662306a36Sopenharmony_ci		    && boot_params.eddbuf_entries < EDDMAXNR) {
16762306a36Sopenharmony_ci			memcpy(edp, &ei, sizeof(ei));
16862306a36Sopenharmony_ci			edp++;
16962306a36Sopenharmony_ci			boot_params.eddbuf_entries++;
17062306a36Sopenharmony_ci		}
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci		if (do_mbr && !read_mbr_sig(devno, &ei, mbrptr++))
17362306a36Sopenharmony_ci			boot_params.edd_mbr_sig_buf_entries = devno-0x80+1;
17462306a36Sopenharmony_ci	}
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	if (!be_quiet)
17762306a36Sopenharmony_ci		printf("ok\n");
17862306a36Sopenharmony_ci}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci#endif
181