11da177e4SLinus Torvalds /* 21da177e4SLinus Torvalds * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com> 31da177e4SLinus Torvalds * Volker Sameske <sameske@de.ibm.com> 41da177e4SLinus Torvalds * Bugreports.to..: <Linux390@de.ibm.com> 5*46e88947SStefan Weinhuber * Copyright IBM Corp. 1999, 2012 61da177e4SLinus Torvalds */ 71da177e4SLinus Torvalds 81da177e4SLinus Torvalds #include <linux/buffer_head.h> 91da177e4SLinus Torvalds #include <linux/hdreg.h> 101da177e4SLinus Torvalds #include <linux/slab.h> 111da177e4SLinus Torvalds #include <asm/dasd.h> 121da177e4SLinus Torvalds #include <asm/ebcdic.h> 131da177e4SLinus Torvalds #include <asm/uaccess.h> 141da177e4SLinus Torvalds #include <asm/vtoc.h> 151da177e4SLinus Torvalds 161da177e4SLinus Torvalds #include "check.h" 171da177e4SLinus Torvalds #include "ibm.h" 181da177e4SLinus Torvalds 19*46e88947SStefan Weinhuber 20*46e88947SStefan Weinhuber union label_t { 21*46e88947SStefan Weinhuber struct vtoc_volume_label_cdl vol; 22*46e88947SStefan Weinhuber struct vtoc_volume_label_ldl lnx; 23*46e88947SStefan Weinhuber struct vtoc_cms_label cms; 24*46e88947SStefan Weinhuber }; 25*46e88947SStefan Weinhuber 261da177e4SLinus Torvalds /* 271da177e4SLinus Torvalds * compute the block number from a 281da177e4SLinus Torvalds * cyl-cyl-head-head structure 291da177e4SLinus Torvalds */ 30*46e88947SStefan Weinhuber static sector_t cchh2blk(struct vtoc_cchh *ptr, struct hd_geometry *geo) 31*46e88947SStefan Weinhuber { 32b44b0ab3SStefan Weinhuber sector_t cyl; 33b44b0ab3SStefan Weinhuber __u16 head; 34b44b0ab3SStefan Weinhuber 35b44b0ab3SStefan Weinhuber /* decode cylinder and heads for large volumes */ 36b44b0ab3SStefan Weinhuber cyl = ptr->hh & 0xFFF0; 37b44b0ab3SStefan Weinhuber cyl <<= 12; 38b44b0ab3SStefan Weinhuber cyl |= ptr->cc; 39b44b0ab3SStefan Weinhuber head = ptr->hh & 0x000F; 40b44b0ab3SStefan Weinhuber return cyl * geo->heads * geo->sectors + 41b44b0ab3SStefan Weinhuber head * geo->sectors; 421da177e4SLinus Torvalds } 431da177e4SLinus Torvalds 441da177e4SLinus Torvalds /* 451da177e4SLinus Torvalds * compute the block number from a 461da177e4SLinus Torvalds * cyl-cyl-head-head-block structure 471da177e4SLinus Torvalds */ 48*46e88947SStefan Weinhuber static sector_t cchhb2blk(struct vtoc_cchhb *ptr, struct hd_geometry *geo) 49*46e88947SStefan Weinhuber { 50b44b0ab3SStefan Weinhuber sector_t cyl; 51b44b0ab3SStefan Weinhuber __u16 head; 52b44b0ab3SStefan Weinhuber 53b44b0ab3SStefan Weinhuber /* decode cylinder and heads for large volumes */ 54b44b0ab3SStefan Weinhuber cyl = ptr->hh & 0xFFF0; 55b44b0ab3SStefan Weinhuber cyl <<= 12; 56b44b0ab3SStefan Weinhuber cyl |= ptr->cc; 57b44b0ab3SStefan Weinhuber head = ptr->hh & 0x000F; 58b44b0ab3SStefan Weinhuber return cyl * geo->heads * geo->sectors + 59b44b0ab3SStefan Weinhuber head * geo->sectors + 601da177e4SLinus Torvalds ptr->b; 611da177e4SLinus Torvalds } 621da177e4SLinus Torvalds 63*46e88947SStefan Weinhuber static int find_label(struct parsed_partitions *state, 64*46e88947SStefan Weinhuber dasd_information2_t *info, 65*46e88947SStefan Weinhuber struct hd_geometry *geo, 66*46e88947SStefan Weinhuber int blocksize, 67*46e88947SStefan Weinhuber sector_t *labelsect, 68*46e88947SStefan Weinhuber char name[], 69*46e88947SStefan Weinhuber char type[], 70*46e88947SStefan Weinhuber union label_t *label) 71*46e88947SStefan Weinhuber { 72*46e88947SStefan Weinhuber Sector sect; 73*46e88947SStefan Weinhuber unsigned char *data; 74*46e88947SStefan Weinhuber sector_t testsect[3]; 75*46e88947SStefan Weinhuber unsigned char temp[5]; 76*46e88947SStefan Weinhuber int found = 0; 77*46e88947SStefan Weinhuber int i, testcount; 78*46e88947SStefan Weinhuber 79*46e88947SStefan Weinhuber /* There a three places where we may find a valid label: 80*46e88947SStefan Weinhuber * - on an ECKD disk it's block 2 81*46e88947SStefan Weinhuber * - on an FBA disk it's block 1 82*46e88947SStefan Weinhuber * - on an CMS formatted FBA disk it is sector 1, even if the block size 83*46e88947SStefan Weinhuber * is larger than 512 bytes (possible if the DIAG discipline is used) 84*46e88947SStefan Weinhuber * If we have a valid info structure, then we know exactly which case we 85*46e88947SStefan Weinhuber * have, otherwise we just search through all possebilities. 86*46e88947SStefan Weinhuber */ 87*46e88947SStefan Weinhuber if (info) { 88*46e88947SStefan Weinhuber if ((info->cu_type == 0x6310 && info->dev_type == 0x9336) || 89*46e88947SStefan Weinhuber (info->cu_type == 0x3880 && info->dev_type == 0x3370)) 90*46e88947SStefan Weinhuber testsect[0] = info->label_block; 91*46e88947SStefan Weinhuber else 92*46e88947SStefan Weinhuber testsect[0] = info->label_block * (blocksize >> 9); 93*46e88947SStefan Weinhuber testcount = 1; 94*46e88947SStefan Weinhuber } else { 95*46e88947SStefan Weinhuber testsect[0] = 1; 96*46e88947SStefan Weinhuber testsect[1] = (blocksize >> 9); 97*46e88947SStefan Weinhuber testsect[2] = 2 * (blocksize >> 9); 98*46e88947SStefan Weinhuber testcount = 3; 99*46e88947SStefan Weinhuber } 100*46e88947SStefan Weinhuber for (i = 0; i < testcount; ++i) { 101*46e88947SStefan Weinhuber data = read_part_sector(state, testsect[i], §); 102*46e88947SStefan Weinhuber if (data == NULL) 103*46e88947SStefan Weinhuber continue; 104*46e88947SStefan Weinhuber memcpy(label, data, sizeof(*label)); 105*46e88947SStefan Weinhuber memcpy(temp, data, 4); 106*46e88947SStefan Weinhuber temp[4] = 0; 107*46e88947SStefan Weinhuber EBCASC(temp, 4); 108*46e88947SStefan Weinhuber put_dev_sector(sect); 109*46e88947SStefan Weinhuber if (!strcmp(temp, "VOL1") || 110*46e88947SStefan Weinhuber !strcmp(temp, "LNX1") || 111*46e88947SStefan Weinhuber !strcmp(temp, "CMS1")) { 112*46e88947SStefan Weinhuber if (!strcmp(temp, "VOL1")) { 113*46e88947SStefan Weinhuber strncpy(type, label->vol.vollbl, 4); 114*46e88947SStefan Weinhuber strncpy(name, label->vol.volid, 6); 115*46e88947SStefan Weinhuber } else { 116*46e88947SStefan Weinhuber strncpy(type, label->lnx.vollbl, 4); 117*46e88947SStefan Weinhuber strncpy(name, label->lnx.volid, 6); 118*46e88947SStefan Weinhuber } 119*46e88947SStefan Weinhuber EBCASC(type, 4); 120*46e88947SStefan Weinhuber EBCASC(name, 6); 121*46e88947SStefan Weinhuber *labelsect = testsect[i]; 122*46e88947SStefan Weinhuber found = 1; 123*46e88947SStefan Weinhuber break; 124*46e88947SStefan Weinhuber } 125*46e88947SStefan Weinhuber } 126*46e88947SStefan Weinhuber if (!found) 127*46e88947SStefan Weinhuber memset(label, 0, sizeof(*label)); 128*46e88947SStefan Weinhuber 129*46e88947SStefan Weinhuber return found; 130*46e88947SStefan Weinhuber } 131*46e88947SStefan Weinhuber 132*46e88947SStefan Weinhuber static int find_vol1_partitions(struct parsed_partitions *state, 133*46e88947SStefan Weinhuber struct hd_geometry *geo, 134*46e88947SStefan Weinhuber int blocksize, 135*46e88947SStefan Weinhuber char name[], 136*46e88947SStefan Weinhuber union label_t *label) 137*46e88947SStefan Weinhuber { 138*46e88947SStefan Weinhuber sector_t blk; 139*46e88947SStefan Weinhuber int counter; 140*46e88947SStefan Weinhuber char tmp[64]; 141*46e88947SStefan Weinhuber Sector sect; 142*46e88947SStefan Weinhuber unsigned char *data; 143*46e88947SStefan Weinhuber loff_t offset, size; 144*46e88947SStefan Weinhuber struct vtoc_format1_label f1; 145*46e88947SStefan Weinhuber int secperblk; 146*46e88947SStefan Weinhuber 147*46e88947SStefan Weinhuber snprintf(tmp, sizeof(tmp), "VOL1/%8s:", name); 148*46e88947SStefan Weinhuber strlcat(state->pp_buf, tmp, PAGE_SIZE); 1491da177e4SLinus Torvalds /* 150*46e88947SStefan Weinhuber * get start of VTOC from the disk label and then search for format1 151*46e88947SStefan Weinhuber * and format8 labels 152*46e88947SStefan Weinhuber */ 153*46e88947SStefan Weinhuber secperblk = blocksize >> 9; 154*46e88947SStefan Weinhuber blk = cchhb2blk(&label->vol.vtoc, geo) + 1; 155*46e88947SStefan Weinhuber counter = 0; 156*46e88947SStefan Weinhuber data = read_part_sector(state, blk * secperblk, §); 157*46e88947SStefan Weinhuber while (data != NULL) { 158*46e88947SStefan Weinhuber memcpy(&f1, data, sizeof(struct vtoc_format1_label)); 159*46e88947SStefan Weinhuber put_dev_sector(sect); 160*46e88947SStefan Weinhuber /* skip FMT4 / FMT5 / FMT7 labels */ 161*46e88947SStefan Weinhuber if (f1.DS1FMTID == _ascebc['4'] 162*46e88947SStefan Weinhuber || f1.DS1FMTID == _ascebc['5'] 163*46e88947SStefan Weinhuber || f1.DS1FMTID == _ascebc['7'] 164*46e88947SStefan Weinhuber || f1.DS1FMTID == _ascebc['9']) { 165*46e88947SStefan Weinhuber blk++; 166*46e88947SStefan Weinhuber data = read_part_sector(state, blk * secperblk, §); 167*46e88947SStefan Weinhuber continue; 168*46e88947SStefan Weinhuber } 169*46e88947SStefan Weinhuber /* only FMT1 and 8 labels valid at this point */ 170*46e88947SStefan Weinhuber if (f1.DS1FMTID != _ascebc['1'] && 171*46e88947SStefan Weinhuber f1.DS1FMTID != _ascebc['8']) 172*46e88947SStefan Weinhuber break; 173*46e88947SStefan Weinhuber /* OK, we got valid partition data */ 174*46e88947SStefan Weinhuber offset = cchh2blk(&f1.DS1EXT1.llimit, geo); 175*46e88947SStefan Weinhuber size = cchh2blk(&f1.DS1EXT1.ulimit, geo) - 176*46e88947SStefan Weinhuber offset + geo->sectors; 177*46e88947SStefan Weinhuber offset *= secperblk; 178*46e88947SStefan Weinhuber size *= secperblk; 179*46e88947SStefan Weinhuber if (counter >= state->limit) 180*46e88947SStefan Weinhuber break; 181*46e88947SStefan Weinhuber put_partition(state, counter + 1, offset, size); 182*46e88947SStefan Weinhuber counter++; 183*46e88947SStefan Weinhuber blk++; 184*46e88947SStefan Weinhuber data = read_part_sector(state, blk * secperblk, §); 185*46e88947SStefan Weinhuber } 186*46e88947SStefan Weinhuber strlcat(state->pp_buf, "\n", PAGE_SIZE); 187*46e88947SStefan Weinhuber 188*46e88947SStefan Weinhuber if (!data) 189*46e88947SStefan Weinhuber return -1; 190*46e88947SStefan Weinhuber 191*46e88947SStefan Weinhuber return 1; 192*46e88947SStefan Weinhuber } 193*46e88947SStefan Weinhuber 194*46e88947SStefan Weinhuber static int find_lnx1_partitions(struct parsed_partitions *state, 195*46e88947SStefan Weinhuber struct hd_geometry *geo, 196*46e88947SStefan Weinhuber int blocksize, 197*46e88947SStefan Weinhuber char name[], 198*46e88947SStefan Weinhuber union label_t *label, 199*46e88947SStefan Weinhuber sector_t labelsect, 200*46e88947SStefan Weinhuber loff_t i_size, 201*46e88947SStefan Weinhuber dasd_information2_t *info) 202*46e88947SStefan Weinhuber { 203*46e88947SStefan Weinhuber loff_t offset, geo_size, size; 204*46e88947SStefan Weinhuber char tmp[64]; 205*46e88947SStefan Weinhuber int secperblk; 206*46e88947SStefan Weinhuber 207*46e88947SStefan Weinhuber snprintf(tmp, sizeof(tmp), "LNX1/%8s:", name); 208*46e88947SStefan Weinhuber strlcat(state->pp_buf, tmp, PAGE_SIZE); 209*46e88947SStefan Weinhuber secperblk = blocksize >> 9; 210*46e88947SStefan Weinhuber if (label->lnx.ldl_version == 0xf2) { 211*46e88947SStefan Weinhuber size = label->lnx.formatted_blocks * secperblk; 212*46e88947SStefan Weinhuber } else { 213*46e88947SStefan Weinhuber /* 214*46e88947SStefan Weinhuber * Formated w/o large volume support. If the sanity check 215*46e88947SStefan Weinhuber * 'size based on geo == size based on i_size' is true, then 216*46e88947SStefan Weinhuber * we can safely assume that we know the formatted size of 217*46e88947SStefan Weinhuber * the disk, otherwise we need additional information 218*46e88947SStefan Weinhuber * that we can only get from a real DASD device. 219*46e88947SStefan Weinhuber */ 220*46e88947SStefan Weinhuber geo_size = geo->cylinders * geo->heads 221*46e88947SStefan Weinhuber * geo->sectors * secperblk; 222*46e88947SStefan Weinhuber size = i_size >> 9; 223*46e88947SStefan Weinhuber if (size != geo_size) { 224*46e88947SStefan Weinhuber if (!info) { 225*46e88947SStefan Weinhuber strlcat(state->pp_buf, "\n", PAGE_SIZE); 226*46e88947SStefan Weinhuber return 1; 227*46e88947SStefan Weinhuber } 228*46e88947SStefan Weinhuber if (!strcmp(info->type, "ECKD")) 229*46e88947SStefan Weinhuber if (geo_size < size) 230*46e88947SStefan Weinhuber size = geo_size; 231*46e88947SStefan Weinhuber /* else keep size based on i_size */ 232*46e88947SStefan Weinhuber } 233*46e88947SStefan Weinhuber } 234*46e88947SStefan Weinhuber /* first and only partition starts in the first block after the label */ 235*46e88947SStefan Weinhuber offset = labelsect + secperblk; 236*46e88947SStefan Weinhuber put_partition(state, 1, offset, size - offset); 237*46e88947SStefan Weinhuber strlcat(state->pp_buf, "\n", PAGE_SIZE); 238*46e88947SStefan Weinhuber return 1; 239*46e88947SStefan Weinhuber } 240*46e88947SStefan Weinhuber 241*46e88947SStefan Weinhuber static int find_cms1_partitions(struct parsed_partitions *state, 242*46e88947SStefan Weinhuber struct hd_geometry *geo, 243*46e88947SStefan Weinhuber int blocksize, 244*46e88947SStefan Weinhuber char name[], 245*46e88947SStefan Weinhuber union label_t *label, 246*46e88947SStefan Weinhuber sector_t labelsect) 247*46e88947SStefan Weinhuber { 248*46e88947SStefan Weinhuber loff_t offset, size; 249*46e88947SStefan Weinhuber char tmp[64]; 250*46e88947SStefan Weinhuber int secperblk; 251*46e88947SStefan Weinhuber 252*46e88947SStefan Weinhuber /* 253*46e88947SStefan Weinhuber * VM style CMS1 labeled disk 254*46e88947SStefan Weinhuber */ 255*46e88947SStefan Weinhuber blocksize = label->cms.block_size; 256*46e88947SStefan Weinhuber secperblk = blocksize >> 9; 257*46e88947SStefan Weinhuber if (label->cms.disk_offset != 0) { 258*46e88947SStefan Weinhuber snprintf(tmp, sizeof(tmp), "CMS1/%8s(MDSK):", name); 259*46e88947SStefan Weinhuber strlcat(state->pp_buf, tmp, PAGE_SIZE); 260*46e88947SStefan Weinhuber /* disk is reserved minidisk */ 261*46e88947SStefan Weinhuber offset = label->cms.disk_offset * secperblk; 262*46e88947SStefan Weinhuber size = (label->cms.block_count - 1) * secperblk; 263*46e88947SStefan Weinhuber } else { 264*46e88947SStefan Weinhuber snprintf(tmp, sizeof(tmp), "CMS1/%8s:", name); 265*46e88947SStefan Weinhuber strlcat(state->pp_buf, tmp, PAGE_SIZE); 266*46e88947SStefan Weinhuber /* 267*46e88947SStefan Weinhuber * Special case for FBA devices: 268*46e88947SStefan Weinhuber * If an FBA device is CMS formatted with blocksize > 512 byte 269*46e88947SStefan Weinhuber * and the DIAG discipline is used, then the CMS label is found 270*46e88947SStefan Weinhuber * in sector 1 instead of block 1. However, the partition is 271*46e88947SStefan Weinhuber * still supposed to start in block 2. 272*46e88947SStefan Weinhuber */ 273*46e88947SStefan Weinhuber if (labelsect == 1) 274*46e88947SStefan Weinhuber offset = 2 * secperblk; 275*46e88947SStefan Weinhuber else 276*46e88947SStefan Weinhuber offset = labelsect + secperblk; 277*46e88947SStefan Weinhuber size = label->cms.block_count * secperblk; 278*46e88947SStefan Weinhuber } 279*46e88947SStefan Weinhuber 280*46e88947SStefan Weinhuber put_partition(state, 1, offset, size-offset); 281*46e88947SStefan Weinhuber strlcat(state->pp_buf, "\n", PAGE_SIZE); 282*46e88947SStefan Weinhuber return 1; 283*46e88947SStefan Weinhuber } 284*46e88947SStefan Weinhuber 285*46e88947SStefan Weinhuber 286*46e88947SStefan Weinhuber /* 287*46e88947SStefan Weinhuber * This is the main function, called by check.c 2881da177e4SLinus Torvalds */ 2891493bf21STejun Heo int ibm_partition(struct parsed_partitions *state) 2901da177e4SLinus Torvalds { 2911493bf21STejun Heo struct block_device *bdev = state->bdev; 292b44b0ab3SStefan Weinhuber int blocksize, res; 293*46e88947SStefan Weinhuber loff_t i_size, offset, size; 294bf1a95a2SStefan Haberland dasd_information2_t *info; 2951da177e4SLinus Torvalds struct hd_geometry *geo; 2961da177e4SLinus Torvalds char type[5] = {0,}; 2971da177e4SLinus Torvalds char name[7] = {0,}; 298cffab6bcSPeter Oberparleiter sector_t labelsect; 299*46e88947SStefan Weinhuber union label_t *label; 3001da177e4SLinus Torvalds 30157881dd9SSuzuki K P res = 0; 302e1defc4fSMartin K. Petersen blocksize = bdev_logical_block_size(bdev); 30390f0094dSHorst Hummel if (blocksize <= 0) 30457881dd9SSuzuki K P goto out_exit; 30590f0094dSHorst Hummel i_size = i_size_read(bdev->bd_inode); 30690f0094dSHorst Hummel if (i_size == 0) 30757881dd9SSuzuki K P goto out_exit; 308bf1a95a2SStefan Haberland info = kmalloc(sizeof(dasd_information2_t), GFP_KERNEL); 309bf1a95a2SStefan Haberland if (info == NULL) 31057881dd9SSuzuki K P goto out_exit; 311bf1a95a2SStefan Haberland geo = kmalloc(sizeof(struct hd_geometry), GFP_KERNEL); 312bf1a95a2SStefan Haberland if (geo == NULL) 3131da177e4SLinus Torvalds goto out_nogeo; 314bf1a95a2SStefan Haberland label = kmalloc(sizeof(union label_t), GFP_KERNEL); 315bf1a95a2SStefan Haberland if (label == NULL) 31656dc6a88SPeter Oberparleiter goto out_nolab; 317*46e88947SStefan Weinhuber if (ioctl_by_bdev(bdev, HDIO_GETGEO, (unsigned long)geo) != 0) 31857881dd9SSuzuki K P goto out_freeall; 319*46e88947SStefan Weinhuber if (ioctl_by_bdev(bdev, BIODASDINFO2, (unsigned long)info) != 0) { 320*46e88947SStefan Weinhuber kfree(info); 321*46e88947SStefan Weinhuber info = NULL; 322b44b0ab3SStefan Weinhuber } 3231da177e4SLinus Torvalds 324*46e88947SStefan Weinhuber if (find_label(state, info, geo, blocksize, &labelsect, name, type, 325*46e88947SStefan Weinhuber label)) { 326*46e88947SStefan Weinhuber if (!strncmp(type, "VOL1", 4)) { 327*46e88947SStefan Weinhuber res = find_vol1_partitions(state, geo, blocksize, name, 328*46e88947SStefan Weinhuber label); 329*46e88947SStefan Weinhuber } else if (!strncmp(type, "LNX1", 4)) { 330*46e88947SStefan Weinhuber res = find_lnx1_partitions(state, geo, blocksize, name, 331*46e88947SStefan Weinhuber label, labelsect, i_size, 332*46e88947SStefan Weinhuber info); 333*46e88947SStefan Weinhuber } else if (!strncmp(type, "CMS1", 4)) { 334*46e88947SStefan Weinhuber res = find_cms1_partitions(state, geo, blocksize, name, 335*46e88947SStefan Weinhuber label, labelsect); 336*46e88947SStefan Weinhuber } 337*46e88947SStefan Weinhuber } else if (info) { 338*46e88947SStefan Weinhuber /* 339*46e88947SStefan Weinhuber * ugly but needed for backward compatibility: 340*46e88947SStefan Weinhuber * If the block device is a DASD (i.e. BIODASDINFO2 works), 341*46e88947SStefan Weinhuber * then we claim it in any case, even though it has no valid 342*46e88947SStefan Weinhuber * label. If it has the LDL format, then we simply define a 343*46e88947SStefan Weinhuber * partition as if it had an LNX1 label. 344*46e88947SStefan Weinhuber */ 34557881dd9SSuzuki K P res = 1; 346bf1a95a2SStefan Haberland if (info->format == DASD_FORMAT_LDL) { 3472041f657SHeiko Carstens strlcat(state->pp_buf, "(nonl)", PAGE_SIZE); 348b44b0ab3SStefan Weinhuber size = i_size >> 9; 349*46e88947SStefan Weinhuber offset = (info->label_block + 1) * (blocksize >> 9); 350*46e88947SStefan Weinhuber put_partition(state, 1, offset, size-offset); 3519c867fbeSAlexey Dobriyan strlcat(state->pp_buf, "\n", PAGE_SIZE); 352*46e88947SStefan Weinhuber } 353*46e88947SStefan Weinhuber } else 354*46e88947SStefan Weinhuber res = 0; 35557881dd9SSuzuki K P 35657881dd9SSuzuki K P out_freeall: 35756dc6a88SPeter Oberparleiter kfree(label); 35856dc6a88SPeter Oberparleiter out_nolab: 3591da177e4SLinus Torvalds kfree(geo); 3601da177e4SLinus Torvalds out_nogeo: 3611da177e4SLinus Torvalds kfree(info); 36257881dd9SSuzuki K P out_exit: 36357881dd9SSuzuki K P return res; 3641da177e4SLinus Torvalds } 365