1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
21da177e4SLinus Torvalds /*
31da177e4SLinus Torvalds * scsicam.c - SCSI CAM support functions, use for HDIO_GETGEO, etc.
41da177e4SLinus Torvalds *
51da177e4SLinus Torvalds * Copyright 1993, 1994 Drew Eckhardt
61da177e4SLinus Torvalds * Visionary Computing
71da177e4SLinus Torvalds * (Unix and Linux consulting and custom programming)
81da177e4SLinus Torvalds * drew@Colorado.EDU
91da177e4SLinus Torvalds * +1 (303) 786-7975
101da177e4SLinus Torvalds *
111da177e4SLinus Torvalds * For more information, please consult the SCSI-CAM draft.
121da177e4SLinus Torvalds */
131da177e4SLinus Torvalds
141da177e4SLinus Torvalds #include <linux/module.h>
155a0e3ad6STejun Heo #include <linux/slab.h>
161da177e4SLinus Torvalds #include <linux/fs.h>
171da177e4SLinus Torvalds #include <linux/kernel.h>
181da177e4SLinus Torvalds #include <linux/blkdev.h>
194ee60ec1SMatthew Wilcox (Oracle) #include <linux/pagemap.h>
201442f76dSChristoph Hellwig #include <linux/msdos_partition.h>
211da177e4SLinus Torvalds #include <asm/unaligned.h>
221da177e4SLinus Torvalds
231da177e4SLinus Torvalds #include <scsi/scsicam.h>
241da177e4SLinus Torvalds
25eb44820cSRob Landley /**
26eb44820cSRob Landley * scsi_bios_ptable - Read PC partition table out of first sector of device.
27eb44820cSRob Landley * @dev: from this device
28eb44820cSRob Landley *
29eb44820cSRob Landley * Description: Reads the first sector from the device and returns %0x42 bytes
30eb44820cSRob Landley * starting at offset %0x1be.
31eb44820cSRob Landley * Returns: partition table in kmalloc(GFP_KERNEL) memory, or NULL on error.
32eb44820cSRob Landley */
scsi_bios_ptable(struct block_device * dev)331da177e4SLinus Torvalds unsigned char *scsi_bios_ptable(struct block_device *dev)
341da177e4SLinus Torvalds {
35a954ea81SChristoph Hellwig struct address_space *mapping = bdev_whole(dev)->bd_inode->i_mapping;
36e63105dfSChristoph Hellwig unsigned char *res = NULL;
37*5fb9bfe0SMatthew Wilcox (Oracle) struct folio *folio;
38e63105dfSChristoph Hellwig
39*5fb9bfe0SMatthew Wilcox (Oracle) folio = read_mapping_folio(mapping, 0, NULL);
40*5fb9bfe0SMatthew Wilcox (Oracle) if (IS_ERR(folio))
41e63105dfSChristoph Hellwig return NULL;
42e63105dfSChristoph Hellwig
43*5fb9bfe0SMatthew Wilcox (Oracle) res = kmemdup(folio_address(folio) + 0x1be, 66, GFP_KERNEL);
44*5fb9bfe0SMatthew Wilcox (Oracle) folio_put(folio);
451da177e4SLinus Torvalds return res;
461da177e4SLinus Torvalds }
471da177e4SLinus Torvalds EXPORT_SYMBOL(scsi_bios_ptable);
481da177e4SLinus Torvalds
49eb44820cSRob Landley /**
50eb44820cSRob Landley * scsi_partsize - Parse cylinders/heads/sectors from PC partition table
51a10183d7SChristoph Hellwig * @bdev: block device to parse
52eb44820cSRob Landley * @capacity: size of the disk in sectors
53a10183d7SChristoph Hellwig * @geom: output in form of [hds, cylinders, sectors]
541da177e4SLinus Torvalds *
55739aca06SMauro Carvalho Chehab * Determine the BIOS mapping/geometry used to create the partition
56a10183d7SChristoph Hellwig * table, storing the results in @geom.
571da177e4SLinus Torvalds *
58a10183d7SChristoph Hellwig * Returns: %false on failure, %true on success.
591da177e4SLinus Torvalds */
scsi_partsize(struct block_device * bdev,sector_t capacity,int geom[3])60a10183d7SChristoph Hellwig bool scsi_partsize(struct block_device *bdev, sector_t capacity, int geom[3])
611da177e4SLinus Torvalds {
621da177e4SLinus Torvalds int cyl, ext_cyl, end_head, end_cyl, end_sector;
631da177e4SLinus Torvalds unsigned int logical_end, physical_end, ext_physical_end;
641442f76dSChristoph Hellwig struct msdos_partition *p, *largest = NULL;
65a10183d7SChristoph Hellwig void *buf;
66a10183d7SChristoph Hellwig int ret = false;
671da177e4SLinus Torvalds
68a10183d7SChristoph Hellwig buf = scsi_bios_ptable(bdev);
69a10183d7SChristoph Hellwig if (!buf)
70a10183d7SChristoph Hellwig return false;
711da177e4SLinus Torvalds
721da177e4SLinus Torvalds if (*(unsigned short *) (buf + 64) == 0xAA55) {
73a10183d7SChristoph Hellwig int largest_cyl = -1, i;
74a10183d7SChristoph Hellwig
75a10183d7SChristoph Hellwig for (i = 0, p = buf; i < 4; i++, p++) {
761da177e4SLinus Torvalds if (!p->sys_ind)
771da177e4SLinus Torvalds continue;
781da177e4SLinus Torvalds #ifdef DEBUG
791da177e4SLinus Torvalds printk("scsicam_bios_param : partition %d has system \n",
801da177e4SLinus Torvalds i);
811da177e4SLinus Torvalds #endif
821da177e4SLinus Torvalds cyl = p->cyl + ((p->sector & 0xc0) << 2);
831da177e4SLinus Torvalds if (cyl > largest_cyl) {
841da177e4SLinus Torvalds largest_cyl = cyl;
851da177e4SLinus Torvalds largest = p;
861da177e4SLinus Torvalds }
871da177e4SLinus Torvalds }
881da177e4SLinus Torvalds }
891da177e4SLinus Torvalds if (largest) {
901da177e4SLinus Torvalds end_cyl = largest->end_cyl + ((largest->end_sector & 0xc0) << 2);
911da177e4SLinus Torvalds end_head = largest->end_head;
921da177e4SLinus Torvalds end_sector = largest->end_sector & 0x3f;
931da177e4SLinus Torvalds
941da177e4SLinus Torvalds if (end_head + 1 == 0 || end_sector == 0)
95a10183d7SChristoph Hellwig goto out_free_buf;
961da177e4SLinus Torvalds
971da177e4SLinus Torvalds #ifdef DEBUG
981da177e4SLinus Torvalds printk("scsicam_bios_param : end at h = %d, c = %d, s = %d\n",
991da177e4SLinus Torvalds end_head, end_cyl, end_sector);
1001da177e4SLinus Torvalds #endif
1011da177e4SLinus Torvalds
1021da177e4SLinus Torvalds physical_end = end_cyl * (end_head + 1) * end_sector +
1031da177e4SLinus Torvalds end_head * end_sector + end_sector;
1041da177e4SLinus Torvalds
1051da177e4SLinus Torvalds /* This is the actual _sector_ number at the end */
106678e2757SChristoph Hellwig logical_end = get_unaligned_le32(&largest->start_sect)
107678e2757SChristoph Hellwig + get_unaligned_le32(&largest->nr_sects);
1081da177e4SLinus Torvalds
1091da177e4SLinus Torvalds /* This is for >1023 cylinders */
1101da177e4SLinus Torvalds ext_cyl = (logical_end - (end_head * end_sector + end_sector))
1111da177e4SLinus Torvalds / (end_head + 1) / end_sector;
1121da177e4SLinus Torvalds ext_physical_end = ext_cyl * (end_head + 1) * end_sector +
1131da177e4SLinus Torvalds end_head * end_sector + end_sector;
1141da177e4SLinus Torvalds
1151da177e4SLinus Torvalds #ifdef DEBUG
1161da177e4SLinus Torvalds printk("scsicam_bios_param : logical_end=%d physical_end=%d ext_physical_end=%d ext_cyl=%d\n"
1171da177e4SLinus Torvalds ,logical_end, physical_end, ext_physical_end, ext_cyl);
1181da177e4SLinus Torvalds #endif
1191da177e4SLinus Torvalds
120a10183d7SChristoph Hellwig if (logical_end == physical_end ||
1211da177e4SLinus Torvalds (end_cyl == 1023 && ext_physical_end == logical_end)) {
122a10183d7SChristoph Hellwig geom[0] = end_head + 1;
123a10183d7SChristoph Hellwig geom[1] = end_sector;
124a10183d7SChristoph Hellwig geom[2] = (unsigned long)capacity /
125a10183d7SChristoph Hellwig ((end_head + 1) * end_sector);
126a10183d7SChristoph Hellwig ret = true;
127a10183d7SChristoph Hellwig goto out_free_buf;
1281da177e4SLinus Torvalds }
1291da177e4SLinus Torvalds #ifdef DEBUG
1301da177e4SLinus Torvalds printk("scsicam_bios_param : logical (%u) != physical (%u)\n",
1311da177e4SLinus Torvalds logical_end, physical_end);
1321da177e4SLinus Torvalds #endif
1331da177e4SLinus Torvalds }
134a10183d7SChristoph Hellwig
135a10183d7SChristoph Hellwig out_free_buf:
136a10183d7SChristoph Hellwig kfree(buf);
137a10183d7SChristoph Hellwig return ret;
1381da177e4SLinus Torvalds }
1391da177e4SLinus Torvalds EXPORT_SYMBOL(scsi_partsize);
1401da177e4SLinus Torvalds
1411da177e4SLinus Torvalds /*
1421da177e4SLinus Torvalds * Function : static int setsize(unsigned long capacity,unsigned int *cyls,
1431da177e4SLinus Torvalds * unsigned int *hds, unsigned int *secs);
1441da177e4SLinus Torvalds *
1451da177e4SLinus Torvalds * Purpose : to determine a near-optimal int 0x13 mapping for a
1461da177e4SLinus Torvalds * SCSI disk in terms of lost space of size capacity, storing
1471da177e4SLinus Torvalds * the results in *cyls, *hds, and *secs.
1481da177e4SLinus Torvalds *
1491da177e4SLinus Torvalds * Returns : -1 on failure, 0 on success.
1501da177e4SLinus Torvalds *
1511da177e4SLinus Torvalds * Extracted from
1521da177e4SLinus Torvalds *
1531da177e4SLinus Torvalds * WORKING X3T9.2
1541da177e4SLinus Torvalds * DRAFT 792D
155eb44820cSRob Landley * see http://www.t10.org/ftp/t10/drafts/cam/cam-r12b.pdf
1561da177e4SLinus Torvalds *
1571da177e4SLinus Torvalds * Revision 6
1581da177e4SLinus Torvalds * 10-MAR-94
1591da177e4SLinus Torvalds * Information technology -
1601da177e4SLinus Torvalds * SCSI-2 Common access method
1611da177e4SLinus Torvalds * transport and SCSI interface module
1621da177e4SLinus Torvalds *
1631da177e4SLinus Torvalds * ANNEX A :
1641da177e4SLinus Torvalds *
1651da177e4SLinus Torvalds * setsize() converts a read capacity value to int 13h
1661da177e4SLinus Torvalds * head-cylinder-sector requirements. It minimizes the value for
1671da177e4SLinus Torvalds * number of heads and maximizes the number of cylinders. This
1681da177e4SLinus Torvalds * will support rather large disks before the number of heads
1691da177e4SLinus Torvalds * will not fit in 4 bits (or 6 bits). This algorithm also
1701da177e4SLinus Torvalds * minimizes the number of sectors that will be unused at the end
1711da177e4SLinus Torvalds * of the disk while allowing for very large disks to be
1721da177e4SLinus Torvalds * accommodated. This algorithm does not use physical geometry.
1731da177e4SLinus Torvalds */
1741da177e4SLinus Torvalds
setsize(unsigned long capacity,unsigned int * cyls,unsigned int * hds,unsigned int * secs)1751da177e4SLinus Torvalds static int setsize(unsigned long capacity, unsigned int *cyls, unsigned int *hds,
1761da177e4SLinus Torvalds unsigned int *secs)
1771da177e4SLinus Torvalds {
1781da177e4SLinus Torvalds unsigned int rv = 0;
1791da177e4SLinus Torvalds unsigned long heads, sectors, cylinders, temp;
1801da177e4SLinus Torvalds
1811da177e4SLinus Torvalds cylinders = 1024L; /* Set number of cylinders to max */
1821da177e4SLinus Torvalds sectors = 62L; /* Maximize sectors per track */
1831da177e4SLinus Torvalds
1841da177e4SLinus Torvalds temp = cylinders * sectors; /* Compute divisor for heads */
1851da177e4SLinus Torvalds heads = capacity / temp; /* Compute value for number of heads */
1861da177e4SLinus Torvalds if (capacity % temp) { /* If no remainder, done! */
1871da177e4SLinus Torvalds heads++; /* Else, increment number of heads */
1881da177e4SLinus Torvalds temp = cylinders * heads; /* Compute divisor for sectors */
1891da177e4SLinus Torvalds sectors = capacity / temp; /* Compute value for sectors per
1901da177e4SLinus Torvalds track */
1911da177e4SLinus Torvalds if (capacity % temp) { /* If no remainder, done! */
1921da177e4SLinus Torvalds sectors++; /* Else, increment number of sectors */
1931da177e4SLinus Torvalds temp = heads * sectors; /* Compute divisor for cylinders */
1941da177e4SLinus Torvalds cylinders = capacity / temp; /* Compute number of cylinders */
1951da177e4SLinus Torvalds }
1961da177e4SLinus Torvalds }
1971da177e4SLinus Torvalds if (cylinders == 0)
1981da177e4SLinus Torvalds rv = (unsigned) -1; /* Give error if 0 cylinders */
1991da177e4SLinus Torvalds
2001da177e4SLinus Torvalds *cyls = (unsigned int) cylinders; /* Stuff return values */
2011da177e4SLinus Torvalds *secs = (unsigned int) sectors;
2021da177e4SLinus Torvalds *hds = (unsigned int) heads;
2031da177e4SLinus Torvalds return (rv);
2041da177e4SLinus Torvalds }
20526ae3533SChristoph Hellwig
20626ae3533SChristoph Hellwig /**
20726ae3533SChristoph Hellwig * scsicam_bios_param - Determine geometry of a disk in cylinders/heads/sectors.
20826ae3533SChristoph Hellwig * @bdev: which device
20926ae3533SChristoph Hellwig * @capacity: size of the disk in sectors
21026ae3533SChristoph Hellwig * @ip: return value: ip[0]=heads, ip[1]=sectors, ip[2]=cylinders
21126ae3533SChristoph Hellwig *
21226ae3533SChristoph Hellwig * Description : determine the BIOS mapping/geometry used for a drive in a
21326ae3533SChristoph Hellwig * SCSI-CAM system, storing the results in ip as required
21426ae3533SChristoph Hellwig * by the HDIO_GETGEO ioctl().
21526ae3533SChristoph Hellwig *
21626ae3533SChristoph Hellwig * Returns : -1 on failure, 0 on success.
21726ae3533SChristoph Hellwig */
scsicam_bios_param(struct block_device * bdev,sector_t capacity,int * ip)21826ae3533SChristoph Hellwig int scsicam_bios_param(struct block_device *bdev, sector_t capacity, int *ip)
21926ae3533SChristoph Hellwig {
22026ae3533SChristoph Hellwig u64 capacity64 = capacity; /* Suppress gcc warning */
221a10183d7SChristoph Hellwig int ret = 0;
22226ae3533SChristoph Hellwig
22326ae3533SChristoph Hellwig /* try to infer mapping from partition table */
224a10183d7SChristoph Hellwig if (scsi_partsize(bdev, capacity, ip))
225a10183d7SChristoph Hellwig return 0;
22626ae3533SChristoph Hellwig
227a10183d7SChristoph Hellwig if (capacity64 < (1ULL << 32)) {
22826ae3533SChristoph Hellwig /*
22926ae3533SChristoph Hellwig * Pick some standard mapping with at most 1024 cylinders, and
23026ae3533SChristoph Hellwig * at most 62 sectors per track - this works up to 7905 MB.
23126ae3533SChristoph Hellwig */
23226ae3533SChristoph Hellwig ret = setsize((unsigned long)capacity, (unsigned int *)ip + 2,
23326ae3533SChristoph Hellwig (unsigned int *)ip + 0, (unsigned int *)ip + 1);
23426ae3533SChristoph Hellwig }
23526ae3533SChristoph Hellwig
23626ae3533SChristoph Hellwig /*
23726ae3533SChristoph Hellwig * If something went wrong, then apparently we have to return a geometry
23826ae3533SChristoph Hellwig * with more than 1024 cylinders.
23926ae3533SChristoph Hellwig */
24026ae3533SChristoph Hellwig if (ret || ip[0] > 255 || ip[1] > 63) {
24126ae3533SChristoph Hellwig if ((capacity >> 11) > 65534) {
24226ae3533SChristoph Hellwig ip[0] = 255;
24326ae3533SChristoph Hellwig ip[1] = 63;
24426ae3533SChristoph Hellwig } else {
24526ae3533SChristoph Hellwig ip[0] = 64;
24626ae3533SChristoph Hellwig ip[1] = 32;
24726ae3533SChristoph Hellwig }
24826ae3533SChristoph Hellwig
24926ae3533SChristoph Hellwig if (capacity > 65535*63*255)
25026ae3533SChristoph Hellwig ip[2] = 65535;
25126ae3533SChristoph Hellwig else
25226ae3533SChristoph Hellwig ip[2] = (unsigned long)capacity / (ip[0] * ip[1]);
25326ae3533SChristoph Hellwig }
25426ae3533SChristoph Hellwig
25526ae3533SChristoph Hellwig return 0;
25626ae3533SChristoph Hellwig }
25726ae3533SChristoph Hellwig EXPORT_SYMBOL(scsicam_bios_param);
258