tcm: Add native 32-byte CDB support
Subsystems (2)
PATCH 1/3 tcm: Add native 32-byte CDB support
From: Nicholas Bellinger <[email protected]>
This patch adds a '#define TCM_MAX_COMMAND_SIZE 32' and converts all inline TCM core and subsystem CDB size defines to use TCM_MAX_COMMAND_SIZE instead of the 16-byte MAX_COMMAND_SIZE. This includes the conversion of MAX_COMMAND_SIZE in certain places to use include/scsi/scsi.h:scsi_command_size() to properly extract the size of a received CDB based on opcode.
This patch also includes the conversion of the FILEIO, IBLOCK, PSCSI, RAMDISK and STGT subsystem modules to use TCM_MAX_COMMAND_SIZE for their default internal inline CDB size.
It also adds transport_lba_64_ext() and transport_get_sectors_32() callers into target_core_transport.c to be used for extracting a 64-bit logical block address and 32-bit transfer length (in block) from 32-byte CDBs. Finally this patch adds split_cdb_XX_32() and split_cdb_XX_32() for handling the generation of 32-byte CDBs for individual struct se_task.
--- drivers/target/target_core_file.h | 2 +- drivers/target/target_core_iblock.h | 2 +- drivers/target/target_core_pscsi.c | 2 +- drivers/target/target_core_pscsi.h | 2 +- drivers/target/target_core_rd.h | 2 +- drivers/target/target_core_scdb.c | 38 +++++++++++++++++++++++++++ drivers/target/target_core_scdb.h | 2 + drivers/target/target_core_stgt.h | 2 +- drivers/target/target_core_transport.c | 44 ++++++++++++++++++++++++++++--- include/target/target_core_base.h | 13 +++++++++ 10 files changed, 98 insertions(+), 11 deletions(-)
drivers/target/target_core_file.h +1 -1
@@ -26,7 +26,7 @@ extern int linux_blockdevice_check(int, int);struct fd_request {/* SCSI CDB from iSCSI Command PDU */ -unsigned char fd_scsi_cdb[MAX_COMMAND_SIZE ]; +unsigned char fd_scsi_cdb[TCM_MAX_COMMAND_SIZE ];/* Data Direction */ u8 fd_data_direction;/* Total length of request */
drivers/target/target_core_iblock.h +1 -1
@@ -12,7 +12,7 @@extern struct se_global *se_global;struct iblock_req { -unsigned char ib_scsi_cdb[MAX_COMMAND_SIZE ]; +unsigned char ib_scsi_cdb[TCM_MAX_COMMAND_SIZE ];atomic_t ib_bio_cnt;u32 ib_sg_count;void *ib_buf;
drivers/target/target_core_pscsi.c +1 -1
@@ -743,7 +743,7 @@ static inline void pscsi_blk_init_request(* Load the referenced struct se_task's SCSI CDB into * include/linux/blkdev.h:struct request->cmd */ - pt->pscsi_req->cmd_len =COMMAND_SIZE (pt->pscsi_cdb[0 ]); + pt->pscsi_req->cmd_len =scsi_command_size (pt->pscsi_cdb);memcpy (pt->pscsi_req->cmd, pt->pscsi_cdb, pt->pscsi_req->cmd_len);/* * Setup pointer for outgoing sense data.
drivers/target/target_core_pscsi.h +1 -1
@@ -28,7 +28,7 @@ extern int linux_blockdevice_check(int, int);#include <linux/kobject.h> struct pscsi_plugin_task { -unsigned char pscsi_cdb[MAX_COMMAND_SIZE ]; +unsigned char pscsi_cdb[TCM_MAX_COMMAND_SIZE ];unsigned char pscsi_sense[SCSI_SENSE_BUFFERSIZE ];int pscsi_direction;int pscsi_result;
drivers/target/target_core_rd.h +1 -1
@@ -31,7 +31,7 @@ void rd_module_exit(void);struct rd_request {/* SCSI CDB from iSCSI Command PDU */ -unsigned char rd_scsi_cdb[MAX_COMMAND_SIZE ]; +unsigned char rd_scsi_cdb[TCM_MAX_COMMAND_SIZE ];/* Data Direction */ u8 rd_data_direction;/* Total length of request */
drivers/target/target_core_scdb.c +38 -0
@@ -28,6 +28,7 @@#include <linux/net.h> #include <linux/string.h> +#include <scsi/scsi.h> #include <target/target_core_base.h> #include <target/target_core_transport.h>
@@ -147,3 +148,40 @@ void split_cdb_RW_16( cdb[0 ] = (rw) ?0x8a :0x88 ;split_cdb_XX_16 (lba, sectors, &cdb[0 ]); } + +/* +* split_cdb_XX_32(): +* +* 64-bit LBA w/ 32-bit SECTORS such as READ_32, WRITE_32 and XDWRITEREAD_32 +*/ +void split_cdb_XX_32 ( +unsigned long long lba, +u32 *sectors, +unsigned char *cdb) +{ + cdb[12 ] = (lba >>56 ) &0xff ; + cdb[13 ] = (lba >>48 ) &0xff ; + cdb[14 ] = (lba >>40 ) &0xff ; + cdb[15 ] = (lba >>32 ) &0xff ; + cdb[16 ] = (lba >>24 ) &0xff ; + cdb[17 ] = (lba >>16 ) &0xff ; + cdb[18 ] = (lba >>8 ) &0xff ; + cdb[19 ] = lba &0xff ; + cdb[28 ] = (*sectors >>24 ) &0xff ; + cdb[29 ] = (*sectors >>16 ) &0xff ; + cdb[30 ] = (*sectors >>8 ) &0xff ; + cdb[31 ] = *sectors &0xff ; +} + +void split_cdb_RW_32 ( +unsigned long long lba, +u32 *sectors, +unsigned char *cdb, +int rw) +{ +/* +* Set service action for VARIABLE_LENGTH_CMD +*/ + cdb[9 ] = (rw) ?WRITE_32 :READ_32 ; +split_cdb_XX_32 (lba, sectors, &cdb[0 ]); +}
drivers/target/target_core_scdb.h +2 -0
@@ -9,5 +9,7 @@ extern void split_cdb_XX_12(unsigned long long, u32 *, unsigned char *);extern void split_cdb_RW_12 (unsigned long long ,u32 *,unsigned char *,int );extern void split_cdb_XX_16 (unsigned long long ,u32 *,unsigned char *);extern void split_cdb_RW_16 (unsigned long long ,u32 *,unsigned char *,int ); +extern void split_cdb_XX_32 (unsigned long long ,u32 *,unsigned char *); +extern void split_cdb_RW_32 (unsigned long long ,u32 *,unsigned char *,int );#endif /* TARGET_CORE_SCDB_H */
drivers/target/target_core_stgt.h +1 -1
@@ -23,7 +23,7 @@ extern int linux_blockdevice_check(int, int);#include <linux/kobject.h> struct stgt_plugin_task { -unsigned char stgt_cdb[MAX_COMMAND_SIZE ]; +unsigned char stgt_cdb[TCM_MAX_COMMAND_SIZE ];unsigned char stgt_sense[SCSI_SENSE_BUFFERSIZE ];int stgt_direction;int stgt_result;
drivers/target/target_core_transport.c +39 -5
86 lines not on this page · raw message
include/target/target_core_base.h +13 -0
@@ -17,6 +17,19 @@/* Maximum Number of LUNs per Target Portal Group */ #define TRANSPORT_MAX_LUNS_PER_TPG 256/* +* By default we use 32-byte CDBs in TCM Core and subsystem plugin code. +* +* Note that both include/scsi/scsi_cmnd.h:MAX_COMMAND_SIZE and +* include/linux/blkdev.h:BLOCK_MAX_CDB as of v2.6.36-rc4 still use +* 16-byte CDBs by default and require an extra allocation for +* 32-byte CDBs to becasue of legacy issues. +* +* Within TCM Core there are no such legacy limitiations, so we go ahead +* use 32-byte CDBs by default and use include/scsi/scsi.h:scsi_command_size() +* within all TCM Core and subsystem plugin code. +*/ +#define TCM_MAX_COMMAND_SIZE 32 +/* * From include/scsi/scsi_cmnd.h:SCSI_SENSE_BUFFERSIZE, currently * defined 96, but the real limit is 252 (or 260 including the header) */
Signature · 1 line
1.5.6.5
Quoted 63 lines · Nicholas A. Bellinger
From: Nicholas Bellinger <[email protected]>
This patch adds a '#define
TCM_MAX_COMMAND_SIZE32' and converts all inline TCM core and subsystem CDB size defines to useTCM_MAX_COMMAND_SIZEinstead of the 16-byteMAX_COMMAND_SIZE. This includes the conversion ofMAX_COMMAND_SIZEin certain places to use include/scsi/scsi.h:scsi_command_size() to properly extract the size of a received CDB based on opcode.This patch also includes the conversion of the FILEIO, IBLOCK, PSCSI, RAMDISK and STGT subsystem modules to use
TCM_MAX_COMMAND_SIZEfor their default internal inline CDB size.It also adds
transport_lba_64_ext()andtransport_get_sectors_32()callers intotarget_core_transport.cto be used for extracting a 64-bit logical block address and 32-bit transfer length (in block) from 32-byte CDBs. Finally this patch addssplit_cdb_XX_32()andsplit_cdb_XX_32()for handling the generation of 32-byte CDBs for individual structse_task.Signed-off-by Nicholas A. Bellinger <[email protected]>--- drivers/target/target_core_file.h | 2 +- drivers/target/target_core_iblock.h | 2 +- drivers/target/target_core_pscsi.c | 2 +- drivers/target/target_core_pscsi.h | 2 +- drivers/target/target_core_rd.h | 2 +- drivers/target/target_core_scdb.c | 38 +++++++++++++++++++++++++++ drivers/target/target_core_scdb.h | 2 + drivers/target/target_core_stgt.h | 2 +- drivers/target/target_core_transport.c | 44 ++++++++++++++++++++++++++++--- include/target/target_core_base.h | 13 +++++++++ 10 files changed, 98 insertions(+), 11 deletions(-)drivers/target/target_core_file.h +1
-1@@ -26,7 +26,7 @@ extern int linux_blockdevice_check(int, int);struct fd_request {/* SCSI CDB from iSCSI Command PDU */ -unsigned char fd_scsi_cdb[MAX_COMMAND_SIZE ]; +unsigned char fd_scsi_cdb[TCM_MAX_COMMAND_SIZE ];/* Data Direction */ u8 fd_data_direction;/* Total length of request */ drivers/target/target_core_iblock.h +1
-1@@ -12,7 +12,7 @@extern struct se_global *se_global;struct iblock_req { -unsigned char ib_scsi_cdb[MAX_COMMAND_SIZE ]; +unsigned char ib_scsi_cdb[TCM_MAX_COMMAND_SIZE ];atomic_t ib_bio_cnt;u32 ib_sg_count;void *ib_buf;drivers/target/target_core_pscsi.c +0
-0
drivers/target/target_core_pscsi.c static inline void pscsi_blk_init_request( · line 747
Quoted 6 lines
drivers/target/target_core_pscsi.c +1
-1@@ -743,7 +743,7 @@ static inline void pscsi_blk_init_request(* Load the referenced struct se_task's SCSI CDB into * include/linux/blkdev.h:struct request->cmd */ - pt->pscsi_req->cmd_len =COMMAND_SIZE (pt->pscsi_cdb[0 ]); + pt->pscsi_req->cmd_len =scsi_command_size (pt->pscsi_cdb);memcpy (pt->pscsi_req->cmd, pt->pscsi_cdb, pt->pscsi_req->cmd_len);
Boom, only 16 bytes at req->cmd.
For larger than 16 bytes commands at the block/scsi-ml level you need to:
request->cmd = my_buffer_valid_until_complete; request->cmd_len = scsi_command_size()
if above "my_buffer_valid_until_complet" is always available you can just do this regardless. If you need to dynamically allocate it (and deallocate) then you should do an if > MAX_COMMAND_SIZE
Quoted 7 lines
drivers/target/target_core_pscsi.c +0
-0/* * Setup pointer for outgoing sense data. drivers/target/target_core_pscsi.h +0
-0
drivers/target/target_core_pscsi.h extern int linux_blockdevice_check(int, int); · line 34
Quoted 8 lines
drivers/target/target_core_pscsi.h +1
-1@@ -28,7 +28,7 @@ extern int linux_blockdevice_check(int, int);#include <linux/kobject.h> struct pscsi_plugin_task { -unsigned char pscsi_cdb[MAX_COMMAND_SIZE ]; +unsigned char pscsi_cdb[TCM_MAX_COMMAND_SIZE ];unsigned char pscsi_sense[SCSI_SENSE_BUFFERSIZE ];int pscsi_direction;int pscsi_result;
pscsi is when you directly Q to a scsi LLD via midlayer right? Note that most scsi LLDs don't support > 16 or 12 bytes. The regular scsi despatch will check this for you and return an error. How are these dispatched? blk_execute_rq* ?
Quoted 38 lines
drivers/target/target_core_rd.h +1
-1@@ -31,7 +31,7 @@ void rd_module_exit(void);struct rd_request {/* SCSI CDB from iSCSI Command PDU */ -unsigned char rd_scsi_cdb[MAX_COMMAND_SIZE ]; +unsigned char rd_scsi_cdb[TCM_MAX_COMMAND_SIZE ];/* Data Direction */ u8 rd_data_direction;/* Total length of request */ drivers/target/target_core_scdb.c +10
-0@@ -28,6 +28,7 @@#include <linux/net.h> #include <linux/string.h> +#include <scsi/scsi.h> #include <target/target_core_base.h> #include <target/target_core_transport.h> @@ -147,3 +148,40 @@ void split_cdb_RW_16( cdb[0 ] = (rw) ?0x8a :0x88 ;split_cdb_XX_16 (lba, sectors, &cdb[0 ]); } + +/* +* split_cdb_XX_32(): +* +* 64-bit LBA w/ 32-bit SECTORS such as READ_32, WRITE_32 and XDWRITEREAD_32 +*/ +void split_cdb_XX_32( + unsigned long long lba, + u32 *sectors,
drivers/target/target_core_scdb.c void split_cdb_RW_16( · line 169
Quoted 10 lines
drivers/target/target_core_scdb.c +10
-0+ unsigned char *cdb) +{ + cdb[12 ] = (lba >>56 ) &0xff ; + cdb[13 ] = (lba >>48 ) &0xff ; + cdb[14 ] = (lba >>40 ) &0xff ; + cdb[15 ] = (lba >>32 ) &0xff ; + cdb[16 ] = (lba >>24 ) &0xff ; + cdb[17 ] = (lba >>16 ) &0xff ; + cdb[18 ] = (lba >>8 ) &0xff ; + cdb[19 ] = lba &0xff ;
put_unaligned_be64
drivers/target/target_core_scdb.c void split_cdb_RW_16( · line 173
Quoted 4 lines
drivers/target/target_core_scdb.c +4
-0+ cdb[28 ] = (*sectors >>24 ) &0xff ; + cdb[29 ] = (*sectors >>16 ) &0xff ; + cdb[30 ] = (*sectors >>8 ) &0xff ; + cdb[31 ] = *sectors &0xff ;
put_unaligned_be32
Quoted 74 lines
drivers/target/target_core_scdb.c +14
-0+} + +void split_cdb_RW_32 ( +unsigned long long lba, +u32 *sectors, +unsigned char *cdb, +int rw) +{ +/* +* Set service action for VARIABLE_LENGTH_CMD +*/ + cdb[9 ] = (rw) ?WRITE_32 :READ_32 ; +split_cdb_XX_32 (lba, sectors, &cdb[0 ]); +}drivers/target/target_core_scdb.h +2
-0@@ -9,5 +9,7 @@ extern void split_cdb_XX_12(unsigned long long, u32 *, unsigned char *);extern void split_cdb_RW_12 (unsigned long long ,u32 *,unsigned char *,int );extern void split_cdb_XX_16 (unsigned long long ,u32 *,unsigned char *);extern void split_cdb_RW_16 (unsigned long long ,u32 *,unsigned char *,int ); +extern void split_cdb_XX_32 (unsigned long long ,u32 *,unsigned char *); +extern void split_cdb_RW_32 (unsigned long long ,u32 *,unsigned char *,int );#endif /* TARGET_CORE_SCDB_H */ drivers/target/target_core_stgt.h +1
-1@@ -23,7 +23,7 @@ extern int linux_blockdevice_check(int, int);#include <linux/kobject.h> struct stgt_plugin_task { -unsigned char stgt_cdb[MAX_COMMAND_SIZE ]; +unsigned char stgt_cdb[TCM_MAX_COMMAND_SIZE ];unsigned char stgt_sense[SCSI_SENSE_BUFFERSIZE ];int stgt_direction;int stgt_result;drivers/target/target_core_transport.c +6
-3@@ -2636,7 +2636,8 @@ static int transport_process_control_sg_transform( cdb =TRANSPORT (dev)->get_cdb (task);if (cdb) -memcpy (cdb,T_TASK (cmd)->t_task_cdb,MAX_COMMAND_SIZE ); +memcpy (cdb,T_TASK (cmd)->t_task_cdb, +scsi_command_size (T_TASK (cmd)->t_task_cdb)); task->task_size = cmd->data_length; task->task_sg_num =1 ;@@ -2677,7 +2678,8 @@ static int transport_process_control_nonsg_transform( cdb =TRANSPORT (dev)->get_cdb (task);if (cdb) -memcpy (cdb,T_TASK (cmd)->t_task_cdb,MAX_COMMAND_SIZE ); +memcpy (cdb,T_TASK (cmd)->t_task_cdb, +scsi_command_size (T_TASK (cmd)->t_task_cdb)); task->task_size = cmd->data_length; task->task_sg_num =0 ;@@ -2711,7 +2713,8 @@ static int transport_process_non_data_transform( cdb =TRANSPORT (dev)->get_cdb (task);if (cdb) -memcpy (cdb,T_TASK (cmd)->t_task_cdb,MAX_COMMAND_SIZE ); +memcpy (cdb,T_TASK (cmd)->t_task_cdb, +scsi_command_size (T_TASK (cmd)->t_task_cdb)); task->task_size = cmd->data_length; task->task_sg_num =0 ;
drivers/target/target_core_transport.c int transport_generic_allocate_tasks( · line 2908
Quoted 5 lines
drivers/target/target_core_transport.c +1
-1@@ -2902,7 +2905,7 @@ int transport_generic_allocate_tasks(/* * Copy the original CDB into T_TASK(cmd). */ -memcpy (T_TASK (cmd)->t_task_cdb, cdb,MAX_COMMAND_SIZE ); +memcpy (T_TASK (cmd)->t_task_cdb, cdb,scsi_command_size (cdb));
scsi_command_size can return up to 260 dependent on command. So you'll need a min() to make sure. I'd do a tsk_cdb_cpy() wrapper that does the proper min() for everyone. Later that wrapper can be made smarter for large commands up to 260.
Quoted 8 lines
drivers/target/target_core_transport.c +1
-0/* * Check for SAM Task Attribute Emulation */ @@ -3829,6 +3832,19 @@ static inline unsigned long long transport_lba_64(unsigned char *cdb)return ((unsigned long long )__v2) | (unsigned long long )__v1 <<32 ; } +/*
drivers/target/target_core_transport.c static inline unsigned long long transport_lba_64(unsigned char *cdb) · line 3845
Quoted 10 lines
drivers/target/target_core_transport.c +10
-0+* For VARIABLE_LENGTH_CDB w/ 32 byte extended CDBs +*/ +static inline unsigned long long transport_lba_64_ext (unsigned char *cdb) +{ +unsigned int __v1, __v2; + + __v1 = (cdb[12 ] <<24 ) | (cdb[13 ] <<16 ) | (cdb[14 ] <<8 ) | cdb[15 ]; + __v2 = (cdb[16 ] <<24 ) | (cdb[17 ] <<16 ) | (cdb[18 ] <<8 ) | cdb[19 ]; + +return ((unsigned long long )__v2) | (unsigned long long )__v1 <<32 ;
get_unaligned_be64(&cdb[12]);
I'd just drop this wrapper
Quoted 14 lines
drivers/target/target_core_transport.c +7
-0+} +/* transport_set_supported_SAM_opcode(): * * @@ -4370,6 +4386,23 @@ type_disk: (cdb[12 ] <<8 ) + cdb[13 ]; } +/* +* Used for VARIABLE_LENGTH_CDB WRITE_32 and READ_32 variants +*/ +static inline u32 transport_get_sectors_32( + unsigned char *cdb,
drivers/target/target_core_transport.c type_disk: · line 4403
Quoted 10 lines
drivers/target/target_core_transport.c +10
-0+ struct se_cmd *cmd, + int *ret) +{ +/* +* Assume TYPE_DISK for non struct se_device objects. +* Use 32-bit sector value. +*/ +return (u32)(cdb[28 ] <<24 ) + (cdb[29 ] <<16 ) + + (cdb[30 ] <<8 ) + cdb[31 ]; +
get_unaligned_be32
Quoted 39 lines
drivers/target/target_core_transport.c +4
-1+} + static inline u32 transport_get_size( u32 sectors, unsigned char *cdb,@@ -7496,7 +7529,8 @@ u32 transport_generic_get_cdb_count( cdb =TRANSPORT (dev)->get_cdb (task);if ((cdb)) { -memcpy (cdb,T_TASK (cmd)->t_task_cdb,MAX_COMMAND_SIZE ); +memcpy (cdb,T_TASK (cmd)->t_task_cdb, +scsi_command_size (T_TASK (cmd)->t_task_cdb)); cmd->transport_split_cdb (task->task_lba, &task->task_sectors, cdb); }include/target/target_core_base.h +13
-0@@ -17,6 +17,19 @@/* Maximum Number of LUNs per Target Portal Group */ #define TRANSPORT_MAX_LUNS_PER_TPG 256/* +* By default we use 32-byte CDBs in TCM Core and subsystem plugin code. +* +* Note that both include/scsi/scsi_cmnd.h:MAX_COMMAND_SIZE and +* include/linux/blkdev.h:BLOCK_MAX_CDB as of v2.6.36-rc4 still use +* 16-byte CDBs by default and require an extra allocation for +* 32-byte CDBs to becasue of legacy issues. +* +* Within TCM Core there are no such legacy limitiations, so we go ahead +* use 32-byte CDBs by default and use include/scsi/scsi.h:scsi_command_size() +* within all TCM Core and subsystem plugin code. +*/ +#define TCM_MAX_COMMAND_SIZE 32 +/* * From include/scsi/scsi_cmnd.h:SCSI_SENSE_BUFFERSIZE, currently * defined 96, but the real limit is 252 (or 260 including the header) */
Quoted 33 lines · Boaz Harrosh
Quoted 32 lines · Nicholas A. Bellinger
From: Nicholas Bellinger <[email protected]>
This patch adds a '#define
TCM_MAX_COMMAND_SIZE32' and converts all inline TCM core and subsystem CDB size defines to useTCM_MAX_COMMAND_SIZEinstead of the 16-byteMAX_COMMAND_SIZE. This includes the conversion ofMAX_COMMAND_SIZEin certain places to use include/scsi/scsi.h:scsi_command_size() to properly extract the size of a received CDB based on opcode.This patch also includes the conversion of the FILEIO, IBLOCK, PSCSI, RAMDISK and STGT subsystem modules to use
TCM_MAX_COMMAND_SIZEfor their default internal inline CDB size.It also adds
transport_lba_64_ext()andtransport_get_sectors_32()callers intotarget_core_transport.cto be used for extracting a 64-bit logical block address and 32-bit transfer length (in block) from 32-byte CDBs. Finally this patch addssplit_cdb_XX_32()andsplit_cdb_XX_32()for handling the generation of 32-byte CDBs for individual structse_task.Signed-off-by Nicholas A. Bellinger <[email protected]>--- drivers/target/target_core_file.h | 2 +- drivers/target/target_core_iblock.h | 2 +- drivers/target/target_core_pscsi.c | 2 +- drivers/target/target_core_pscsi.h | 2 +- drivers/target/target_core_rd.h | 2 +- drivers/target/target_core_scdb.c | 38 +++++++++++++++++++++++++++ drivers/target/target_core_scdb.h | 2 + drivers/target/target_core_stgt.h | 2 +- drivers/target/target_core_transport.c | 44 ++++++++++++++++++++++++++++--- include/target/target_core_base.h | 13 +++++++++ 10 files changed, 98 insertions(+), 11 deletions(-)
<SNIP>
Quoted 14 lines
drivers/target/target_core_pscsi.c static inline void pscsi_blk_init_request( · line 747
Quoted 11 lines
drivers/target/target_core_pscsi.c +1
-1@@ -743,7 +743,7 @@ static inline void pscsi_blk_init_request(* Load the referenced struct se_task's SCSI CDB into * include/linux/blkdev.h:struct request->cmd */ - pt->pscsi_req->cmd_len =COMMAND_SIZE (pt->pscsi_cdb[0 ]); + pt->pscsi_req->cmd_len =scsi_command_size (pt->pscsi_cdb);memcpy (pt->pscsi_req->cmd, pt->pscsi_cdb, pt->pscsi_req->cmd_len);Boom, only 16 bytes at
req->cmd.
Quoted 9 lines
For larger than 16 bytes commands at the
block/scsi-mllevel you need to:request->cmd = my_buffer_valid_until_complete; request->cmd_len = scsi_command_size()if above "
my_buffer_valid_until_complet" is always available you can just do this regardless. If you need to dynamically allocate it (and deallocate) then you should do an if >MAX_COMMAND_SIZE
Hi Boaz,
I was pretty there was going to be some more work required on the TCM/PSCSI case for proper > 16 byte support, thanks for spotting this one.
So what I am thinking is that we can get rid of this memcpy() all together and just always use the per struct se_task allocated struct pscsi_plugin_task *pt->pscsi_cdb[] up to the new TCM_MAX_COMMAND_SIZE (32 bytes) for the request->cmd pointer.
Is there anything special that needs to be done in order to singal that struct request->cmd will be using *tp->pscsi_cdb[], and not struct request->__cmd[BLK_MAX_CMD]..?
Quoted 9 lines
Quoted 9 lines
drivers/target/target_core_pscsi.c +0
-0/* * Setup pointer for outgoing sense data. drivers/target/target_core_pscsi.h +0
-0@@ -28,7 +28,7 @@ extern int linux_blockdevice_check(int, int);#include <linux/kobject.h>
Quoted 11 lines
drivers/target/target_core_pscsi.h extern int linux_blockdevice_check(int, int); · line 34
Quoted 6 lines
drivers/target/target_core_pscsi.h +1
-1struct pscsi_plugin_task { -unsigned char pscsi_cdb[MAX_COMMAND_SIZE ]; +unsigned char pscsi_cdb[TCM_MAX_COMMAND_SIZE ];unsigned char pscsi_sense[SCSI_SENSE_BUFFERSIZE ];int pscsi_direction;int pscsi_result;pscsi is when you directly Q to a scsi LLD via midlayer right? Note that most scsi LLDs don't support > 16 or 12 bytes. The regular scsi despatch will check this for you and return an error. How are these dispatched? blk_execute_rq* ?
Correct on both points. So all control path CDBs with PSCSI are treated as a passthrough. The one special thing about pSCSI and struct request passthrough is wrt SCF_SCSI_DATA_SG_IO_CDB I/Os is that TCM Core will still handle the 1 -> N mapping of TCM struct se_task->task_sg[] across a single struct se_cmd->t_mem_list for received CDB sectors > underying HW max_sectors limitiations reported by pscsi_get_max_sectors().
Quoted 42 lines
Quoted 42 lines
drivers/target/target_core_rd.h +1
-1@@ -31,7 +31,7 @@ void rd_module_exit(void);struct rd_request {/* SCSI CDB from iSCSI Command PDU */ -unsigned char rd_scsi_cdb[MAX_COMMAND_SIZE ]; +unsigned char rd_scsi_cdb[TCM_MAX_COMMAND_SIZE ];/* Data Direction */ u8 rd_data_direction;/* Total length of request */ drivers/target/target_core_scdb.c +14
-0@@ -28,6 +28,7 @@#include <linux/net.h> #include <linux/string.h> +#include <scsi/scsi.h> #include <target/target_core_base.h> #include <target/target_core_transport.h> @@ -147,3 +148,40 @@ void split_cdb_RW_16( cdb[0 ] = (rw) ?0x8a :0x88 ;split_cdb_XX_16 (lba, sectors, &cdb[0 ]); } + +/* +* split_cdb_XX_32(): +* +* 64-bit LBA w/ 32-bit SECTORS such as READ_32, WRITE_32 and XDWRITEREAD_32 +*/ +void split_cdb_XX_32 ( +unsigned long long lba, +u32 *sectors, +unsigned char *cdb) +{ + cdb[12 ] = (lba >>56 ) &0xff ; + cdb[13 ] = (lba >>48 ) &0xff ;
Quoted 8 lines
drivers/target/target_core_scdb.c void split_cdb_RW_16( · line 169
Quoted 6 lines
drivers/target/target_core_scdb.c +6
-0+ cdb[14 ] = (lba >>40 ) &0xff ; + cdb[15 ] = (lba >>32 ) &0xff ; + cdb[16 ] = (lba >>24 ) &0xff ; + cdb[17 ] = (lba >>16 ) &0xff ; + cdb[18 ] = (lba >>8 ) &0xff ; + cdb[19 ] = lba &0xff ;
put_unaligned_be64
Ok, converting all functions these in target_core_scdb.c to use put_unaligned_be*().
Quoted 7 lines
drivers/target/target_core_scdb.c void split_cdb_RW_16( · line 173
Quoted 5 lines
drivers/target/target_core_scdb.c +4
-0+ cdb[28 ] = (*sectors >>24 ) &0xff ; + cdb[29 ] = (*sectors >>16 ) &0xff ; + cdb[30 ] = (*sectors >>8 ) &0xff ; + cdb[31 ] = *sectors &0xff ;
put_unaligned_be32
<nod>
Quoted 32 lines
Quoted 32 lines
drivers/target/target_core_transport.c +6
-3@@ -2636,7 +2636,8 @@ static int transport_process_control_sg_transform( cdb =TRANSPORT (dev)->get_cdb (task);if (cdb) -memcpy (cdb,T_TASK (cmd)->t_task_cdb,MAX_COMMAND_SIZE ); +memcpy (cdb,T_TASK (cmd)->t_task_cdb, +scsi_command_size (T_TASK (cmd)->t_task_cdb)); task->task_size = cmd->data_length; task->task_sg_num =1 ;@@ -2677,7 +2678,8 @@ static int transport_process_control_nonsg_transform( cdb =TRANSPORT (dev)->get_cdb (task);if (cdb) -memcpy (cdb,T_TASK (cmd)->t_task_cdb,MAX_COMMAND_SIZE ); +memcpy (cdb,T_TASK (cmd)->t_task_cdb, +scsi_command_size (T_TASK (cmd)->t_task_cdb)); task->task_size = cmd->data_length; task->task_sg_num =0 ;@@ -2711,7 +2713,8 @@ static int transport_process_non_data_transform( cdb =TRANSPORT (dev)->get_cdb (task);if (cdb) -memcpy (cdb,T_TASK (cmd)->t_task_cdb,MAX_COMMAND_SIZE ); +memcpy (cdb,T_TASK (cmd)->t_task_cdb, +scsi_command_size (T_TASK (cmd)->t_task_cdb));
Quoted 13 lines
drivers/target/target_core_transport.c int transport_generic_allocate_tasks( · line 2908
Quoted 8 lines
drivers/target/target_core_transport.c +1
-1task->task_size = cmd->data_length; task->task_sg_num =0 ;@@ -2902,7 +2905,7 @@ int transport_generic_allocate_tasks(/* * Copy the original CDB into T_TASK(cmd). */ -memcpy (T_TASK (cmd)->t_task_cdb, cdb,MAX_COMMAND_SIZE ); +memcpy (T_TASK (cmd)->t_task_cdb, cdb,scsi_command_size (cdb));
scsi_command_sizecan return up to 260 dependent on command. So you'll need amin()to make sure. I'd do atsk_cdb_cpy()wrapper that does the propermin()for everyone. Later that wrapper can be made smarter for large commands up to 260.
Hmmm actually, I am wondering if we need a method for the individual backstore to report the maximum supported CDB size on a TCM subsystem plugin basis, say via a caller include/target/target_core_transport.h:
struct se_subsystem_api, and fail the extended CDB at TCM level for the pSCSI cases where struct scsi_host->max_cmd_len < scsi_command_size().
Also wrt to 260 byte CDBs for TCM/pSCSI and really everything received greater than the new default 32 for TCM_MAX_COMMAND_SIZE, this will require the extra allocation by TCM Core at both the T_TASK(cmd)->t_task_cdb[] as well as allowing pSCSI to handle this case.
I will look at implementing this soon, but I still need a way to test this code with TCM/PSCSI and scsi_debug.. Any ideas on where to look for doing this with > 32 byte CDBs..?
Quoted 22 lines
Quoted 9 lines
drivers/target/target_core_transport.c +1
-0/* * Check for SAM Task Attribute Emulation */ @@ -3829,6 +3832,19 @@ static inline unsigned long long transport_lba_64(unsigned char *cdb)return ((unsigned long long )__v2) | (unsigned long long )__v1 <<32 ; } +/* drivers/target/target_core_transport.c static inline unsigned long long transport_lba_64(unsigned char *cdb) · line 3845
Quoted 10 lines
drivers/target/target_core_transport.c +10
-0+* For VARIABLE_LENGTH_CDB w/ 32 byte extended CDBs +*/ +static inline unsigned long long transport_lba_64_ext (unsigned char *cdb) +{ +unsigned int __v1, __v2; + + __v1 = (cdb[12 ] <<24 ) | (cdb[13 ] <<16 ) | (cdb[14 ] <<8 ) | cdb[15 ]; + __v2 = (cdb[16 ] <<24 ) | (cdb[17 ] <<16 ) | (cdb[18 ] <<8 ) | cdb[19 ]; + +return ((unsigned long long )__v2) | (unsigned long long )__v1 <<32 ;
get_unaligned_be64(&cdb[12]);
Quoted 1 line
I'd just drop this wrapper
Good point, I believe this is only used in a single location. transport_generic_allocate_tasks().
Quoted 19 lines
Quoted 19 lines
drivers/target/target_core_transport.c +11
-0+} +/* transport_set_supported_SAM_opcode(): * * @@ -4370,6 +4386,23 @@ type_disk: (cdb[12 ] <<8 ) + cdb[13 ]; } +/* +* Used for VARIABLE_LENGTH_CDB WRITE_32 and READ_32 variants +*/ +static inline u32 transport_get_sectors_32 ( +unsigned char *cdb, +struct se_cmd *cmd, +int *ret) +{ +/*
Quoted 8 lines
drivers/target/target_core_transport.c type_disk: · line 4403
Quoted 6 lines
drivers/target/target_core_transport.c +6
-0+* Assume TYPE_DISK for non struct se_device objects. +* Use 32-bit sector value. +*/ +return (u32)(cdb[28 ] <<24 ) + (cdb[29 ] <<16 ) + + (cdb[30 ] <<8 ) + cdb[31 ]; +
get_unaligned_be32
Ok, there are a few more of these that can be converted to get_unaligned_be*() and friends.
Thanks for your comments Boaz!
--nab
Quoted 67 lines · Nicholas A. Bellinger
Quoted 33 lines · Boaz Harrosh
Quoted 32 lines · Nicholas A. Bellinger
From: Nicholas Bellinger <[email protected]>
This patch adds a '#define
TCM_MAX_COMMAND_SIZE32' and converts all inline TCM core and subsystem CDB size defines to useTCM_MAX_COMMAND_SIZEinstead of the 16-byteMAX_COMMAND_SIZE. This includes the conversion ofMAX_COMMAND_SIZEin certain places to use include/scsi/scsi.h:scsi_command_size() to properly extract the size of a received CDB based on opcode.This patch also includes the conversion of the FILEIO, IBLOCK, PSCSI, RAMDISK and STGT subsystem modules to use
TCM_MAX_COMMAND_SIZEfor their default internal inline CDB size.It also adds
transport_lba_64_ext()andtransport_get_sectors_32()callers intotarget_core_transport.cto be used for extracting a 64-bit logical block address and 32-bit transfer length (in block) from 32-byte CDBs. Finally this patch addssplit_cdb_XX_32()andsplit_cdb_XX_32()for handling the generation of 32-byte CDBs for individual structse_task.Signed-off-by Nicholas A. Bellinger <[email protected]>--- drivers/target/target_core_file.h | 2 +- drivers/target/target_core_iblock.h | 2 +- drivers/target/target_core_pscsi.c | 2 +- drivers/target/target_core_pscsi.h | 2 +- drivers/target/target_core_rd.h | 2 +- drivers/target/target_core_scdb.c | 38 +++++++++++++++++++++++++++ drivers/target/target_core_scdb.h | 2 + drivers/target/target_core_stgt.h | 2 +- drivers/target/target_core_transport.c | 44 ++++++++++++++++++++++++++++--- include/target/target_core_base.h | 13 +++++++++ 10 files changed, 98 insertions(+), 11 deletions(-)<SNIP>
Quoted 14 lines
drivers/target/target_core_pscsi.c static inline void pscsi_blk_init_request( · line 747
Quoted 11 lines
drivers/target/target_core_pscsi.c +1
-1@@ -743,7 +743,7 @@ static inline void pscsi_blk_init_request(* Load the referenced struct se_task's SCSI CDB into * include/linux/blkdev.h:struct request->cmd */ - pt->pscsi_req->cmd_len =COMMAND_SIZE (pt->pscsi_cdb[0 ]); + pt->pscsi_req->cmd_len =scsi_command_size (pt->pscsi_cdb);memcpy (pt->pscsi_req->cmd, pt->pscsi_cdb, pt->pscsi_req->cmd_len);Boom, only 16 bytes at
req->cmd.Quoted 9 lines
For larger than 16 bytes commands at the
block/scsi-mllevel you need to:request->cmd = my_buffer_valid_until_complete; request->cmd_len = scsi_command_size()if above "
my_buffer_valid_until_complet" is always available you can just do this regardless. If you need to dynamically allocate it (and deallocate) then you should do an if >MAX_COMMAND_SIZEHi Boaz,
I was pretty there was going to be some more work required on the TCM/PSCSI case for proper > 16 byte support, thanks for spotting this one.
Quoted 8 lines
So what I am thinking is that we can get rid of this
memcpy()all together and just always use the per structse_taskallocated structpscsi_plugin_task*pt->pscsi_cdb[] up to the newTCM_MAX_COMMAND_SIZE(32 bytes) for therequest->cmdpointer.Is there anything special that needs to be done in order to singal that struct
request->cmdwill be using *tp->pscsi_cdb[], and not struct request->__cmd[BLK_MAX_CMD]..?
No that's perfect.
The tricky part in other places is to keep it allocated through the life cycle of the request. (And overriding the completion for de-allocation). But here we have no problems, the governing pscsi_plugin_task is available until after the request is complete, right?
Quoted 145 lines
Quoted 10 lines
Quoted 10 lines
drivers/target/target_core_pscsi.c +0
-0/* * Setup pointer for outgoing sense data. drivers/target/target_core_pscsi.h +0
-0@@ -28,7 +28,7 @@ extern int linux_blockdevice_check(int, int);#include <linux/kobject.h> Quoted 11 lines
drivers/target/target_core_pscsi.h extern int linux_blockdevice_check(int, int); · line 34
Quoted 6 lines
drivers/target/target_core_pscsi.h +1
-1struct pscsi_plugin_task { -unsigned char pscsi_cdb[MAX_COMMAND_SIZE ]; +unsigned char pscsi_cdb[TCM_MAX_COMMAND_SIZE ];unsigned char pscsi_sense[SCSI_SENSE_BUFFERSIZE ];int pscsi_direction;int pscsi_result;pscsi is when you directly Q to a scsi LLD via midlayer right? Note that most scsi LLDs don't support > 16 or 12 bytes. The regular scsi despatch will check this for you and return an error. How are these dispatched? blk_execute_rq* ?
Correct on both points. So all control path CDBs with PSCSI are treated as a passthrough. The one special thing about pSCSI and struct request passthrough is wrt
SCF_SCSI_DATA_SG_IO_CDBI/Os is that TCM Core will still handle the 1 -> N mapping of TCM struct se_task->task_sg[] across a single structse_cmd->t_mem_listfor received CDB sectors > underying HWmax_sectorslimitiations reported bypscsi_get_max_sectors().Quoted 42 lines
Quoted 42 lines
drivers/target/target_core_rd.h +1
-1@@ -31,7 +31,7 @@ void rd_module_exit(void);struct rd_request {/* SCSI CDB from iSCSI Command PDU */ -unsigned char rd_scsi_cdb[MAX_COMMAND_SIZE ]; +unsigned char rd_scsi_cdb[TCM_MAX_COMMAND_SIZE ];/* Data Direction */ u8 rd_data_direction;/* Total length of request */ drivers/target/target_core_scdb.c +14
-0@@ -28,6 +28,7 @@#include <linux/net.h> #include <linux/string.h> +#include <scsi/scsi.h> #include <target/target_core_base.h> #include <target/target_core_transport.h> @@ -147,3 +148,40 @@ void split_cdb_RW_16( cdb[0 ] = (rw) ?0x8a :0x88 ;split_cdb_XX_16 (lba, sectors, &cdb[0 ]); } + +/* +* split_cdb_XX_32(): +* +* 64-bit LBA w/ 32-bit SECTORS such as READ_32, WRITE_32 and XDWRITEREAD_32 +*/ +void split_cdb_XX_32 ( +unsigned long long lba, +u32 *sectors, +unsigned char *cdb) +{ + cdb[12 ] = (lba >>56 ) &0xff ; + cdb[13 ] = (lba >>48 ) &0xff ;Quoted 8 lines
drivers/target/target_core_scdb.c void split_cdb_RW_16( · line 169
Quoted 6 lines
drivers/target/target_core_scdb.c +6
-0+ cdb[14 ] = (lba >>40 ) &0xff ; + cdb[15 ] = (lba >>32 ) &0xff ; + cdb[16 ] = (lba >>24 ) &0xff ; + cdb[17 ] = (lba >>16 ) &0xff ; + cdb[18 ] = (lba >>8 ) &0xff ; + cdb[19 ] = lba &0xff ;
put_unaligned_be64Ok, converting all functions these in
target_core_scdb.cto use put_unaligned_be*().Quoted 7 lines
drivers/target/target_core_scdb.c void split_cdb_RW_16( · line 173
Quoted 5 lines
drivers/target/target_core_scdb.c +4
-0+ cdb[28 ] = (*sectors >>24 ) &0xff ; + cdb[29 ] = (*sectors >>16 ) &0xff ; + cdb[30 ] = (*sectors >>8 ) &0xff ; + cdb[31 ] = *sectors &0xff ;
put_unaligned_be32<nod>
Quoted 32 lines
Quoted 32 lines
drivers/target/target_core_transport.c +6
-3@@ -2636,7 +2636,8 @@ static int transport_process_control_sg_transform( cdb =TRANSPORT (dev)->get_cdb (task);if (cdb) -memcpy (cdb,T_TASK (cmd)->t_task_cdb,MAX_COMMAND_SIZE ); +memcpy (cdb,T_TASK (cmd)->t_task_cdb, +scsi_command_size (T_TASK (cmd)->t_task_cdb)); task->task_size = cmd->data_length; task->task_sg_num =1 ;@@ -2677,7 +2678,8 @@ static int transport_process_control_nonsg_transform( cdb =TRANSPORT (dev)->get_cdb (task);if (cdb) -memcpy (cdb,T_TASK (cmd)->t_task_cdb,MAX_COMMAND_SIZE ); +memcpy (cdb,T_TASK (cmd)->t_task_cdb, +scsi_command_size (T_TASK (cmd)->t_task_cdb)); task->task_size = cmd->data_length; task->task_sg_num =0 ;@@ -2711,7 +2713,8 @@ static int transport_process_non_data_transform( cdb =TRANSPORT (dev)->get_cdb (task);if (cdb) -memcpy (cdb,T_TASK (cmd)->t_task_cdb,MAX_COMMAND_SIZE ); +memcpy (cdb,T_TASK (cmd)->t_task_cdb, +scsi_command_size (T_TASK (cmd)->t_task_cdb));Quoted 13 lines
drivers/target/target_core_transport.c int transport_generic_allocate_tasks( · line 2908
Quoted 8 lines
drivers/target/target_core_transport.c +1
-1task->task_size = cmd->data_length; task->task_sg_num =0 ;@@ -2902,7 +2905,7 @@ int transport_generic_allocate_tasks(/* * Copy the original CDB into T_TASK(cmd). */ -memcpy (T_TASK (cmd)->t_task_cdb, cdb,MAX_COMMAND_SIZE ); +memcpy (T_TASK (cmd)->t_task_cdb, cdb,scsi_command_size (cdb));
scsi_command_sizecan return up to 260 dependent on command. So you'll need amin()to make sure. I'd do atsk_cdb_cpy()wrapper that does the propermin()for everyone. Later that wrapper can be made smarter for large commands up to 260.Hmmm actually, I am wondering if we need a method for the individual backstore to report the maximum supported CDB size on a TCM subsystem plugin basis, say via a caller
include/target/target_core_transport.h:
structse_subsystem_api, and fail the extended CDB at TCM level for the pSCSI cases where structscsi_host->max_cmd_len<scsi_command_size().
Quoted 5 lines
Also wrt to 260 byte CDBs for TCM/pSCSI and really everything received greater than the new default 32 for
TCM_MAX_COMMAND_SIZE, this will require the extra allocation by TCM Core at both the T_TASK(cmd)->t_task_cdb[] as well as allowing pSCSI to handle this case.
For me, (I'm not sure how hard it is. I still did not look, you know ;-)) I'd drop the allocation all together and just use a pointer and a size. So for instance pSCSI can just pass along it's pointer and size, all the way to the end and back. In something like iscsi the fixed 16 bytes can just point to the received header, and with an extended cdb spill over a buffer should be allocated, then de-allocated once command-response is sent.
So basically allocated once in transport, passed around as pointer. (BTW Same for sense buffer)
And by the way, I'd let the target reject un-known commands. It's a big boy and it's easiest to add new code. No other places to change. (And some fun things can be done, like target filtering). Passing the "size" test says nothing, the actual command needs implementing. Just make sure Error handling is solid. (The target already as a default: return error case)
Quoted 4 lines
I will look at implementing this soon, but I still need a way to test this code with TCM/PSCSI and
scsi_debug.. Any ideas on where to look for doing this with > 32 byte CDBs..?
That's easy. You have decided that TCM_MAX_COMMAND_SIZE == 32 and anything bigger needs allocation. So for the duration of the test set TCM_MAX_COMMAND_SIZE == 16 and see how these 32s get allocated.
or
How ready is target_core_stgt.h can you pass commands to user-space and back. We can send OSD commands.
or
What if we use pscsi_plugin_task to send commands to an iscsi-initiator device that's an OSD. A bit stupid as an admin point of view, but a grate pass through exercise.
[osd-initiator] --net--> [(iscsi_tcm) - (pscsi_plugin_task) - (iscsi-initiator)] \
--net--> [osd-target]
The [osd-initiator] and [osd-target] parts are easy.
Boaz
Quoted 62 lines
Quoted 22 lines
Quoted 9 lines
drivers/target/target_core_transport.c +1
-0/* * Check for SAM Task Attribute Emulation */ @@ -3829,6 +3832,19 @@ static inline unsigned long long transport_lba_64(unsigned char *cdb)return ((unsigned long long )__v2) | (unsigned long long )__v1 <<32 ; } +/* drivers/target/target_core_transport.c static inline unsigned long long transport_lba_64(unsigned char *cdb) · line 3845
Quoted 10 lines
drivers/target/target_core_transport.c +10
-0+* For VARIABLE_LENGTH_CDB w/ 32 byte extended CDBs +*/ +static inline unsigned long long transport_lba_64_ext (unsigned char *cdb) +{ +unsigned int __v1, __v2; + + __v1 = (cdb[12 ] <<24 ) | (cdb[13 ] <<16 ) | (cdb[14 ] <<8 ) | cdb[15 ]; + __v2 = (cdb[16 ] <<24 ) | (cdb[17 ] <<16 ) | (cdb[18 ] <<8 ) | cdb[19 ]; + +return ((unsigned long long )__v2) | (unsigned long long )__v1 <<32 ;
get_unaligned_be64(&cdb[12]);Quoted 1 line
I'd just drop this wrapper
Good point, I believe this is only used in a single location.
transport_generic_allocate_tasks().Quoted 19 lines
Quoted 19 lines
drivers/target/target_core_transport.c +11
-0+} +/* transport_set_supported_SAM_opcode(): * * @@ -4370,6 +4386,23 @@ type_disk: (cdb[12 ] <<8 ) + cdb[13 ]; } +/* +* Used for VARIABLE_LENGTH_CDB WRITE_32 and READ_32 variants +*/ +static inline u32 transport_get_sectors_32 ( +unsigned char *cdb, +struct se_cmd *cmd, +int *ret) +{ +/* Quoted 8 lines
drivers/target/target_core_transport.c type_disk: · line 4403
Quoted 6 lines
drivers/target/target_core_transport.c +6
-0+* Assume TYPE_DISK for non struct se_device objects. +* Use 32-bit sector value. +*/ +return (u32)(cdb[28 ] <<24 ) + (cdb[29 ] <<16 ) + + (cdb[30 ] <<8 ) + cdb[31 ]; +
get_unaligned_be32Ok, there are a few more of these that can be converted to get_unaligned_be*() and friends.
Thanks for your comments Boaz!
--nab