Tomo, Please find attached a patch to TGTD that adds emulation of SBC UNMAP command. The unmap command is implemented so that when the LUN attribute "--params thin_provisioning=1" is set it will try to release the affected region of the LUN image by calling fallocate(... FALLOC_FL_PUNCH_HOLE ...) to release the blocks back to the underlying filesystem. This only works for some filesystems under linux at this point. READ_CAPACITY16 is updated to set LPBME and LBPRZ for thin-provisioned LUNs. VPD page b2 is added which reports the support for provisioning. When the thin_provisioning attribute is updated we update the VPD page accordingly too. regards ronnie sahlberg
Attachment:
0002-SBC-UNMAP-Add-support-for-thin-provisioning-and-the-.patch.gz
Description: GNU Zip compressed data
From 152c5a8947828f0f1681d4f1ab1b91ed7567a1fe Mon Sep 17 00:00:00 2001 From: Ronnie Sahlberg <ronniesahlberg@xxxxxxxxx> Date: Sat, 31 Mar 2012 14:10:39 +1100 Subject: [PATCH 2/2] SBC UNMAP: Add support for thin-provisioning and the UNMAP command. The UNMAP command is implemented using FALLOC_FL_PUNCH_HOLE and will release UNMAPPED blocks back to the underlying filesystem. FALLOC_FL_PUNCH_HOLE is fairly new addition to Linux but works on ext4 and XFS filesystems currently. Signed-off-by: Ronnie Sahlberg <ronniesahlberg@xxxxxxxxx> --- doc/tgtadm.8.xml | 25 +++++++++++++++++ usr/bs_rdwr.c | 45 ++++++++++++++++++++++++++++++++ usr/sbc.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++- usr/scsi.h | 1 + usr/spc.c | 43 +++++++++++++++++++++++++++--- usr/target.c | 2 + usr/tgtd.h | 2 + 7 files changed, 189 insertions(+), 5 deletions(-) diff --git a/doc/tgtadm.8.xml b/doc/tgtadm.8.xml index 668e184..a40f659 100644 --- a/doc/tgtadm.8.xml +++ b/doc/tgtadm.8.xml @@ -352,6 +352,31 @@ tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 1 \ --params readonly=1 </screen> + <varlistentry><term><option>thin_provisioning=<0|1></option></term> + <listitem> + <para> + This controls the provisioning for the LUN. A thin-provisioned + LUN is represented as a sparse file. + TGTD supports provisioning type 2 for sparse files. + When initiators use the SCSI UNMAP command TGTD will release + the affected areas back to the filesystem using + FALLOC_FL_PUNCH_HOLE. + </para> + <para> + This parameter only applies to DISK devices. + </para> + <para> + Thin-provisioning only works for LUNs stored on filesystems + that support FALLOC_FL_PUNCH_HOLE. + </para> + </listitem> + </varlistentry> + + <screen format="linespecific"> +tgtadm --lld iscsi --mode logicalunit --op update --tid 1 --lun 1 \ + --params thin_provisioning=1 + </screen> + </variablelist> </refsect1> diff --git a/usr/bs_rdwr.c b/usr/bs_rdwr.c index 84ed278..d3b6bec 100644 --- a/usr/bs_rdwr.c +++ b/usr/bs_rdwr.c @@ -23,6 +23,7 @@ #include <errno.h> #include <fcntl.h> +#include <linux/falloc.h> #include <inttypes.h> #include <stdio.h> #include <stdlib.h> @@ -160,6 +161,50 @@ static void bs_rdwr_request(struct scsi_cmd *cmd) free(tmpbuf); break; +#ifdef FALLOC_FL_PUNCH_HOLE + case UNMAP: + length = scsi_get_out_length(cmd); + tmpbuf = scsi_get_out_buffer(cmd); + + if (length < 8) + break; + + length -= 8; + tmpbuf += 8; + + while (length >= 16) { + uint64_t lba; + uint64_t tl; + + lba = (uint64_t)tmpbuf[0] << 56 + | (uint64_t)tmpbuf[1] << 48 + | (uint64_t)tmpbuf[2] << 40 + | (uint64_t)tmpbuf[3] << 32 + | (uint64_t)tmpbuf[4] << 24 + | (uint64_t)tmpbuf[5] << 16 + | (uint64_t)tmpbuf[6] << 8 + | (uint64_t)tmpbuf[7]; + + tl = (uint64_t)tmpbuf[8] << 24 + | (uint64_t)tmpbuf[9] << 16 + | (uint64_t)tmpbuf[10] << 8 + | (uint64_t)tmpbuf[11]; + + if (fallocate(fd, + FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE, + lba << cmd->dev->blk_shift, + tl << cmd->dev->blk_shift) != 0) { + eprintf("Failed to punch hole for UNMAP" + " at LBA:" PRIu64 + " TL: " PRIu64 "\n", + lba, tl); + } + + length -= 16; + tmpbuf += 16; + } + break; +#endif default: break; } diff --git a/usr/sbc.c b/usr/sbc.c index 0c3681e..c93d5de 100644 --- a/usr/sbc.c +++ b/usr/sbc.c @@ -141,6 +141,60 @@ sense: return SAM_STAT_CHECK_CONDITION; } +static int sbc_unmap(int host_no, struct scsi_cmd *cmd) +{ + int ret; + unsigned char key = ILLEGAL_REQUEST; + uint16_t asc = ASC_LUN_NOT_SUPPORTED; + struct scsi_lu *lu = cmd->dev; + int anchor; + + ret = device_reserved(cmd); + if (ret) + return SAM_STAT_RESERVATION_CONFLICT; + + /* We dont support anchored blocks */ + anchor = cmd->scb[1] & 0x01; + if (anchor) { + key = ILLEGAL_REQUEST; + asc = ASC_INVALID_FIELD_IN_CDB; + goto sense; + } + + if (!lu->attrs.thinprovisioning) { + key = ILLEGAL_REQUEST; + asc = ASC_INVALID_OP_CODE; + goto sense; + } + + if (lu->attrs.removable && !lu->attrs.online) { + key = NOT_READY; + asc = ASC_MEDIUM_NOT_PRESENT; + goto sense; + } + + if (lu->attrs.readonly) { + key = DATA_PROTECT; + asc = ASC_WRITE_PROTECT; + goto sense; + } + + ret = cmd->dev->bst->bs_cmd_submit(cmd); + if (ret) { + key = HARDWARE_ERROR; + asc = ASC_INTERNAL_TGT_FAILURE; + goto sense; + } + +sense: + cmd->offset = 0; + scsi_set_in_resid_by_actual(cmd, 0); + scsi_set_out_resid_by_actual(cmd, 0); + + sense_data_build(cmd, key, asc); + return SAM_STAT_CHECK_CONDITION; +} + static int sbc_rw(int host_no, struct scsi_cmd *cmd) { int ret; @@ -370,8 +424,11 @@ static int sbc_service_action(int host_no, struct scsi_cmd *cmd) data[2] = __cpu_to_be32(1UL << bshift); val = (cmd->dev->attrs.lbppbe << 16) | cmd->dev->attrs.la_lba; + if (cmd->dev->attrs.thinprovisioning) + val |= (3 << 14); /* set LBPME and LBPRZ */ data[3] = __cpu_to_be32(val); + overflow: scsi_set_in_resid_by_actual(cmd, len); return SAM_STAT_GOOD; @@ -549,7 +606,24 @@ static struct device_type_template sbc_template = { {spc_illegal_op,}, {spc_illegal_op,}, - [0x40 ... 0x4f] = {spc_illegal_op,}, + /* 0x40 */ + {spc_illegal_op,}, + {spc_illegal_op,}, + {sbc_unmap,}, + {spc_illegal_op,}, + {spc_illegal_op,}, + {spc_illegal_op,}, + {spc_illegal_op,}, + {spc_illegal_op,}, + + {spc_illegal_op,}, + {spc_illegal_op,}, + {spc_illegal_op,}, + {spc_illegal_op,}, + {spc_illegal_op,}, + {spc_illegal_op,}, + {spc_illegal_op,}, + {spc_illegal_op,}, /* 0x50 */ {spc_illegal_op,}, diff --git a/usr/scsi.h b/usr/scsi.h index ca1109a..1508cc6 100644 --- a/usr/scsi.h +++ b/usr/scsi.h @@ -60,6 +60,7 @@ #define WRITE_LONG 0x3f #define CHANGE_DEFINITION 0x40 #define WRITE_SAME 0x41 +#define UNMAP 0x42 #define READ_TOC 0x43 #define LOG_SELECT 0x4c #define LOG_SENSE 0x4d diff --git a/usr/spc.c b/usr/spc.c index 44cd193..1834038 100644 --- a/usr/spc.c +++ b/usr/spc.c @@ -139,6 +139,24 @@ static void update_vpd_83(struct scsi_lu *lu, void *id) strncpy((char *)data + 4, id, SCSI_ID_LEN); } +static void update_vpd_b2(struct scsi_lu *lu, void *id) +{ + struct vpd *vpd_pg = lu->attrs.lu_vpd[PCODE_OFFSET(0xb2)]; + uint8_t *data = vpd_pg->data; + + if (lu->attrs.thinprovisioning) { + data[0] = 0; /* threshold exponent */ + data[1] = 0x84; /* LBPU LBPRZ */ + data[2] = 0x02; /* provisioning type */ + data[3] = 0; + } else { + data[0] = 0; + data[1] = 0; + data[2] = 0; + data[3] = 0; + } +} + static void update_b0_opt_xfer_gran(struct scsi_lu *lu, int opt_xfer_gran) { struct vpd *vpd_pg = lu->attrs.lu_vpd[PCODE_OFFSET(0xb0)]; @@ -1661,7 +1679,7 @@ enum { Opt_removable, Opt_readonly, Opt_online, Opt_mode_page, Opt_path, - Opt_bsoflags, + Opt_bsoflags, Opt_thinprovisioning, Opt_err, }; @@ -1682,6 +1700,7 @@ static match_table_t tokens = { {Opt_mode_page, "mode_page=%s"}, {Opt_path, "path=%s"}, {Opt_bsoflags, "bsoflags=%s"}, + {Opt_thinprovisioning, "thin_provisioning=%s"}, {Opt_err, NULL}, }; @@ -1772,6 +1791,12 @@ tgtadm_err lu_config(struct scsi_lu *lu, char *params, match_fn_t *fn) match_strncpy(buf, &args[0], sizeof(buf)); attrs->readonly = atoi(buf); break; + case Opt_thinprovisioning: + match_strncpy(buf, &args[0], sizeof(buf)); + attrs->thinprovisioning = atoi(buf); + /* update the provisioning vpd page */ + lu_vpd[PCODE_OFFSET(0xb2)]->vpd_update(lu, NULL); + break; case Opt_online: match_strncpy(buf, &args[0], sizeof(buf)); if (atoi(buf)) @@ -1807,6 +1832,10 @@ int spc_lu_init(struct scsi_lu *lu) lu->attrs.device_type = lu->dev_type_template.type; lu->attrs.qualifier = 0x0; + lu->attrs.thinprovisioning = 0; + lu->attrs.removable = 0; + lu->attrs.readonly = 0; + lu->attrs.sense_format = 0; snprintf(lu->attrs.vendor_id, sizeof(lu->attrs.vendor_id), "%-16s", VENDOR_ID); @@ -1839,9 +1868,15 @@ int spc_lu_init(struct scsi_lu *lu) if (!lu_vpd[pg]) return -ENOMEM; - lu->attrs.removable = 0; - lu->attrs.readonly = 0; - lu->attrs.sense_format = 0; + /* VPD page 0xb2 LOGICAL BLOCK PROVISIONING*/ + pg = PCODE_OFFSET(0xb2); + lu_vpd[pg] = alloc_vpd(LBP_VPD_LEN); + if (!lu_vpd[pg]) + return -ENOMEM; + lu_vpd[pg]->vpd_update = update_vpd_b2; + lu_vpd[pg]->vpd_update(lu, NULL); + + lu->dev_type_template.lu_offline(lu); return 0; diff --git a/usr/target.c b/usr/target.c index 0b6be13..dd3ca91 100644 --- a/usr/target.c +++ b/usr/target.c @@ -1857,6 +1857,7 @@ tgtadm_err tgt_target_show_all(struct concat_buf *b) _TAB3 "Removable media: %s\n" _TAB3 "Prevent removal: %s\n" _TAB3 "Readonly: %s\n" + _TAB3 "Thin-provisioning: %s\n" _TAB3 "Backing store type: %s\n" _TAB3 "Backing store path: %s\n" _TAB3 "Backing store flags: %s\n", @@ -1871,6 +1872,7 @@ tgtadm_err tgt_target_show_all(struct concat_buf *b) lu_prevent_removal(lu) ? "Yes" : "No", lu->attrs.readonly ? "Yes" : "No", + lu->attrs.thinprovisioning ? "Yes" : "No", lu->bst ? (lu->bst->bs_name ? : "Unknown") : "None", diff --git a/usr/tgtd.h b/usr/tgtd.h index 726a3f5..03036ba 100644 --- a/usr/tgtd.h +++ b/usr/tgtd.h @@ -14,6 +14,7 @@ struct concat_buf; #define PRODUCT_ID_LEN 16 #define PRODUCT_REV_LEN 4 #define BLOCK_LIMITS_VPD_LEN 0x3C +#define LBP_VPD_LEN 4 #define PCODE_SHIFT 7 #define PCODE_OFFSET(x) (x & ((1 << PCODE_SHIFT) - 1)) @@ -72,6 +73,7 @@ struct lu_phy_attr { char qualifier; /* Peripheral Qualifier */ char removable; /* Removable media */ char readonly; /* Read-Only media */ + char thinprovisioning; /* Use thin-provisioning for this LUN */ char online; /* Logical Unit online */ char sense_format; /* Descrptor format sense data supported */ /* For the following see READ CAPACITY (16) */ -- 1.7.3.1