18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * scsicam.c - SCSI CAM support functions, use for HDIO_GETGEO, etc. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 1993, 1994 Drew Eckhardt 68c2ecf20Sopenharmony_ci * Visionary Computing 78c2ecf20Sopenharmony_ci * (Unix and Linux consulting and custom programming) 88c2ecf20Sopenharmony_ci * drew@Colorado.EDU 98c2ecf20Sopenharmony_ci * +1 (303) 786-7975 108c2ecf20Sopenharmony_ci * 118c2ecf20Sopenharmony_ci * For more information, please consult the SCSI-CAM draft. 128c2ecf20Sopenharmony_ci */ 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci#include <linux/slab.h> 168c2ecf20Sopenharmony_ci#include <linux/fs.h> 178c2ecf20Sopenharmony_ci#include <linux/genhd.h> 188c2ecf20Sopenharmony_ci#include <linux/kernel.h> 198c2ecf20Sopenharmony_ci#include <linux/blkdev.h> 208c2ecf20Sopenharmony_ci#include <linux/msdos_partition.h> 218c2ecf20Sopenharmony_ci#include <asm/unaligned.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci#include <scsi/scsicam.h> 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci/** 268c2ecf20Sopenharmony_ci * scsi_bios_ptable - Read PC partition table out of first sector of device. 278c2ecf20Sopenharmony_ci * @dev: from this device 288c2ecf20Sopenharmony_ci * 298c2ecf20Sopenharmony_ci * Description: Reads the first sector from the device and returns %0x42 bytes 308c2ecf20Sopenharmony_ci * starting at offset %0x1be. 318c2ecf20Sopenharmony_ci * Returns: partition table in kmalloc(GFP_KERNEL) memory, or NULL on error. 328c2ecf20Sopenharmony_ci */ 338c2ecf20Sopenharmony_ciunsigned char *scsi_bios_ptable(struct block_device *dev) 348c2ecf20Sopenharmony_ci{ 358c2ecf20Sopenharmony_ci struct address_space *mapping = dev->bd_contains->bd_inode->i_mapping; 368c2ecf20Sopenharmony_ci unsigned char *res = NULL; 378c2ecf20Sopenharmony_ci struct page *page; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci page = read_mapping_page(mapping, 0, NULL); 408c2ecf20Sopenharmony_ci if (IS_ERR(page)) 418c2ecf20Sopenharmony_ci return NULL; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci if (!PageError(page)) 448c2ecf20Sopenharmony_ci res = kmemdup(page_address(page) + 0x1be, 66, GFP_KERNEL); 458c2ecf20Sopenharmony_ci put_page(page); 468c2ecf20Sopenharmony_ci return res; 478c2ecf20Sopenharmony_ci} 488c2ecf20Sopenharmony_ciEXPORT_SYMBOL(scsi_bios_ptable); 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci/** 518c2ecf20Sopenharmony_ci * scsi_partsize - Parse cylinders/heads/sectors from PC partition table 528c2ecf20Sopenharmony_ci * @bdev: block device to parse 538c2ecf20Sopenharmony_ci * @capacity: size of the disk in sectors 548c2ecf20Sopenharmony_ci * @geom: output in form of [hds, cylinders, sectors] 558c2ecf20Sopenharmony_ci * 568c2ecf20Sopenharmony_ci * Determine the BIOS mapping/geometry used to create the partition 578c2ecf20Sopenharmony_ci * table, storing the results in @geom. 588c2ecf20Sopenharmony_ci * 598c2ecf20Sopenharmony_ci * Returns: %false on failure, %true on success. 608c2ecf20Sopenharmony_ci */ 618c2ecf20Sopenharmony_cibool scsi_partsize(struct block_device *bdev, sector_t capacity, int geom[3]) 628c2ecf20Sopenharmony_ci{ 638c2ecf20Sopenharmony_ci int cyl, ext_cyl, end_head, end_cyl, end_sector; 648c2ecf20Sopenharmony_ci unsigned int logical_end, physical_end, ext_physical_end; 658c2ecf20Sopenharmony_ci struct msdos_partition *p, *largest = NULL; 668c2ecf20Sopenharmony_ci void *buf; 678c2ecf20Sopenharmony_ci int ret = false; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci buf = scsi_bios_ptable(bdev); 708c2ecf20Sopenharmony_ci if (!buf) 718c2ecf20Sopenharmony_ci return false; 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci if (*(unsigned short *) (buf + 64) == 0xAA55) { 748c2ecf20Sopenharmony_ci int largest_cyl = -1, i; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci for (i = 0, p = buf; i < 4; i++, p++) { 778c2ecf20Sopenharmony_ci if (!p->sys_ind) 788c2ecf20Sopenharmony_ci continue; 798c2ecf20Sopenharmony_ci#ifdef DEBUG 808c2ecf20Sopenharmony_ci printk("scsicam_bios_param : partition %d has system \n", 818c2ecf20Sopenharmony_ci i); 828c2ecf20Sopenharmony_ci#endif 838c2ecf20Sopenharmony_ci cyl = p->cyl + ((p->sector & 0xc0) << 2); 848c2ecf20Sopenharmony_ci if (cyl > largest_cyl) { 858c2ecf20Sopenharmony_ci largest_cyl = cyl; 868c2ecf20Sopenharmony_ci largest = p; 878c2ecf20Sopenharmony_ci } 888c2ecf20Sopenharmony_ci } 898c2ecf20Sopenharmony_ci } 908c2ecf20Sopenharmony_ci if (largest) { 918c2ecf20Sopenharmony_ci end_cyl = largest->end_cyl + ((largest->end_sector & 0xc0) << 2); 928c2ecf20Sopenharmony_ci end_head = largest->end_head; 938c2ecf20Sopenharmony_ci end_sector = largest->end_sector & 0x3f; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci if (end_head + 1 == 0 || end_sector == 0) 968c2ecf20Sopenharmony_ci goto out_free_buf; 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_ci#ifdef DEBUG 998c2ecf20Sopenharmony_ci printk("scsicam_bios_param : end at h = %d, c = %d, s = %d\n", 1008c2ecf20Sopenharmony_ci end_head, end_cyl, end_sector); 1018c2ecf20Sopenharmony_ci#endif 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci physical_end = end_cyl * (end_head + 1) * end_sector + 1048c2ecf20Sopenharmony_ci end_head * end_sector + end_sector; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci /* This is the actual _sector_ number at the end */ 1078c2ecf20Sopenharmony_ci logical_end = get_unaligned_le32(&largest->start_sect) 1088c2ecf20Sopenharmony_ci + get_unaligned_le32(&largest->nr_sects); 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci /* This is for >1023 cylinders */ 1118c2ecf20Sopenharmony_ci ext_cyl = (logical_end - (end_head * end_sector + end_sector)) 1128c2ecf20Sopenharmony_ci / (end_head + 1) / end_sector; 1138c2ecf20Sopenharmony_ci ext_physical_end = ext_cyl * (end_head + 1) * end_sector + 1148c2ecf20Sopenharmony_ci end_head * end_sector + end_sector; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci#ifdef DEBUG 1178c2ecf20Sopenharmony_ci printk("scsicam_bios_param : logical_end=%d physical_end=%d ext_physical_end=%d ext_cyl=%d\n" 1188c2ecf20Sopenharmony_ci ,logical_end, physical_end, ext_physical_end, ext_cyl); 1198c2ecf20Sopenharmony_ci#endif 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci if (logical_end == physical_end || 1228c2ecf20Sopenharmony_ci (end_cyl == 1023 && ext_physical_end == logical_end)) { 1238c2ecf20Sopenharmony_ci geom[0] = end_head + 1; 1248c2ecf20Sopenharmony_ci geom[1] = end_sector; 1258c2ecf20Sopenharmony_ci geom[2] = (unsigned long)capacity / 1268c2ecf20Sopenharmony_ci ((end_head + 1) * end_sector); 1278c2ecf20Sopenharmony_ci ret = true; 1288c2ecf20Sopenharmony_ci goto out_free_buf; 1298c2ecf20Sopenharmony_ci } 1308c2ecf20Sopenharmony_ci#ifdef DEBUG 1318c2ecf20Sopenharmony_ci printk("scsicam_bios_param : logical (%u) != physical (%u)\n", 1328c2ecf20Sopenharmony_ci logical_end, physical_end); 1338c2ecf20Sopenharmony_ci#endif 1348c2ecf20Sopenharmony_ci } 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ciout_free_buf: 1378c2ecf20Sopenharmony_ci kfree(buf); 1388c2ecf20Sopenharmony_ci return ret; 1398c2ecf20Sopenharmony_ci} 1408c2ecf20Sopenharmony_ciEXPORT_SYMBOL(scsi_partsize); 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci/* 1438c2ecf20Sopenharmony_ci * Function : static int setsize(unsigned long capacity,unsigned int *cyls, 1448c2ecf20Sopenharmony_ci * unsigned int *hds, unsigned int *secs); 1458c2ecf20Sopenharmony_ci * 1468c2ecf20Sopenharmony_ci * Purpose : to determine a near-optimal int 0x13 mapping for a 1478c2ecf20Sopenharmony_ci * SCSI disk in terms of lost space of size capacity, storing 1488c2ecf20Sopenharmony_ci * the results in *cyls, *hds, and *secs. 1498c2ecf20Sopenharmony_ci * 1508c2ecf20Sopenharmony_ci * Returns : -1 on failure, 0 on success. 1518c2ecf20Sopenharmony_ci * 1528c2ecf20Sopenharmony_ci * Extracted from 1538c2ecf20Sopenharmony_ci * 1548c2ecf20Sopenharmony_ci * WORKING X3T9.2 1558c2ecf20Sopenharmony_ci * DRAFT 792D 1568c2ecf20Sopenharmony_ci * see http://www.t10.org/ftp/t10/drafts/cam/cam-r12b.pdf 1578c2ecf20Sopenharmony_ci * 1588c2ecf20Sopenharmony_ci * Revision 6 1598c2ecf20Sopenharmony_ci * 10-MAR-94 1608c2ecf20Sopenharmony_ci * Information technology - 1618c2ecf20Sopenharmony_ci * SCSI-2 Common access method 1628c2ecf20Sopenharmony_ci * transport and SCSI interface module 1638c2ecf20Sopenharmony_ci * 1648c2ecf20Sopenharmony_ci * ANNEX A : 1658c2ecf20Sopenharmony_ci * 1668c2ecf20Sopenharmony_ci * setsize() converts a read capacity value to int 13h 1678c2ecf20Sopenharmony_ci * head-cylinder-sector requirements. It minimizes the value for 1688c2ecf20Sopenharmony_ci * number of heads and maximizes the number of cylinders. This 1698c2ecf20Sopenharmony_ci * will support rather large disks before the number of heads 1708c2ecf20Sopenharmony_ci * will not fit in 4 bits (or 6 bits). This algorithm also 1718c2ecf20Sopenharmony_ci * minimizes the number of sectors that will be unused at the end 1728c2ecf20Sopenharmony_ci * of the disk while allowing for very large disks to be 1738c2ecf20Sopenharmony_ci * accommodated. This algorithm does not use physical geometry. 1748c2ecf20Sopenharmony_ci */ 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_cistatic int setsize(unsigned long capacity, unsigned int *cyls, unsigned int *hds, 1778c2ecf20Sopenharmony_ci unsigned int *secs) 1788c2ecf20Sopenharmony_ci{ 1798c2ecf20Sopenharmony_ci unsigned int rv = 0; 1808c2ecf20Sopenharmony_ci unsigned long heads, sectors, cylinders, temp; 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci cylinders = 1024L; /* Set number of cylinders to max */ 1838c2ecf20Sopenharmony_ci sectors = 62L; /* Maximize sectors per track */ 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci temp = cylinders * sectors; /* Compute divisor for heads */ 1868c2ecf20Sopenharmony_ci heads = capacity / temp; /* Compute value for number of heads */ 1878c2ecf20Sopenharmony_ci if (capacity % temp) { /* If no remainder, done! */ 1888c2ecf20Sopenharmony_ci heads++; /* Else, increment number of heads */ 1898c2ecf20Sopenharmony_ci temp = cylinders * heads; /* Compute divisor for sectors */ 1908c2ecf20Sopenharmony_ci sectors = capacity / temp; /* Compute value for sectors per 1918c2ecf20Sopenharmony_ci track */ 1928c2ecf20Sopenharmony_ci if (capacity % temp) { /* If no remainder, done! */ 1938c2ecf20Sopenharmony_ci sectors++; /* Else, increment number of sectors */ 1948c2ecf20Sopenharmony_ci temp = heads * sectors; /* Compute divisor for cylinders */ 1958c2ecf20Sopenharmony_ci cylinders = capacity / temp; /* Compute number of cylinders */ 1968c2ecf20Sopenharmony_ci } 1978c2ecf20Sopenharmony_ci } 1988c2ecf20Sopenharmony_ci if (cylinders == 0) 1998c2ecf20Sopenharmony_ci rv = (unsigned) -1; /* Give error if 0 cylinders */ 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci *cyls = (unsigned int) cylinders; /* Stuff return values */ 2028c2ecf20Sopenharmony_ci *secs = (unsigned int) sectors; 2038c2ecf20Sopenharmony_ci *hds = (unsigned int) heads; 2048c2ecf20Sopenharmony_ci return (rv); 2058c2ecf20Sopenharmony_ci} 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci/** 2088c2ecf20Sopenharmony_ci * scsicam_bios_param - Determine geometry of a disk in cylinders/heads/sectors. 2098c2ecf20Sopenharmony_ci * @bdev: which device 2108c2ecf20Sopenharmony_ci * @capacity: size of the disk in sectors 2118c2ecf20Sopenharmony_ci * @ip: return value: ip[0]=heads, ip[1]=sectors, ip[2]=cylinders 2128c2ecf20Sopenharmony_ci * 2138c2ecf20Sopenharmony_ci * Description : determine the BIOS mapping/geometry used for a drive in a 2148c2ecf20Sopenharmony_ci * SCSI-CAM system, storing the results in ip as required 2158c2ecf20Sopenharmony_ci * by the HDIO_GETGEO ioctl(). 2168c2ecf20Sopenharmony_ci * 2178c2ecf20Sopenharmony_ci * Returns : -1 on failure, 0 on success. 2188c2ecf20Sopenharmony_ci */ 2198c2ecf20Sopenharmony_ciint scsicam_bios_param(struct block_device *bdev, sector_t capacity, int *ip) 2208c2ecf20Sopenharmony_ci{ 2218c2ecf20Sopenharmony_ci u64 capacity64 = capacity; /* Suppress gcc warning */ 2228c2ecf20Sopenharmony_ci int ret = 0; 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci /* try to infer mapping from partition table */ 2258c2ecf20Sopenharmony_ci if (scsi_partsize(bdev, capacity, ip)) 2268c2ecf20Sopenharmony_ci return 0; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci if (capacity64 < (1ULL << 32)) { 2298c2ecf20Sopenharmony_ci /* 2308c2ecf20Sopenharmony_ci * Pick some standard mapping with at most 1024 cylinders, and 2318c2ecf20Sopenharmony_ci * at most 62 sectors per track - this works up to 7905 MB. 2328c2ecf20Sopenharmony_ci */ 2338c2ecf20Sopenharmony_ci ret = setsize((unsigned long)capacity, (unsigned int *)ip + 2, 2348c2ecf20Sopenharmony_ci (unsigned int *)ip + 0, (unsigned int *)ip + 1); 2358c2ecf20Sopenharmony_ci } 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci /* 2388c2ecf20Sopenharmony_ci * If something went wrong, then apparently we have to return a geometry 2398c2ecf20Sopenharmony_ci * with more than 1024 cylinders. 2408c2ecf20Sopenharmony_ci */ 2418c2ecf20Sopenharmony_ci if (ret || ip[0] > 255 || ip[1] > 63) { 2428c2ecf20Sopenharmony_ci if ((capacity >> 11) > 65534) { 2438c2ecf20Sopenharmony_ci ip[0] = 255; 2448c2ecf20Sopenharmony_ci ip[1] = 63; 2458c2ecf20Sopenharmony_ci } else { 2468c2ecf20Sopenharmony_ci ip[0] = 64; 2478c2ecf20Sopenharmony_ci ip[1] = 32; 2488c2ecf20Sopenharmony_ci } 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci if (capacity > 65535*63*255) 2518c2ecf20Sopenharmony_ci ip[2] = 65535; 2528c2ecf20Sopenharmony_ci else 2538c2ecf20Sopenharmony_ci ip[2] = (unsigned long)capacity / (ip[0] * ip[1]); 2548c2ecf20Sopenharmony_ci } 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci return 0; 2578c2ecf20Sopenharmony_ci} 2588c2ecf20Sopenharmony_ciEXPORT_SYMBOL(scsicam_bios_param); 259