Below is a document (in Markdown) discussing the means I used to remove this limitation... I tried to respect the SCSI MMC-3 standard and the means I used should
Frankly, I don't have much hope of getting this patch accepted upstream..
# Using the Linux USB mass storage gadget for ISO file greater than 2.2GB
The linux mass storage gadget is capable of simulating CDROMs using an ISO backing file
either with [the g_mass_storage](
https://linux-sunxi.org/USB_Gadget/Mass_storage)
legacy driver, or the [configfs f_mass_storage](
https://wiki.tizen.org/USB/Linux_USB_Layers/Configfs_Composite_Gadget/Usage_eq._to_g_mass_storage.ko)
driver. However, the code of the linux driver includes the code block
```C
if (num_sectors >= 256*60*75) {
num_sectors = 256*60*75 - 1;
LINFO(curlun, "file too big: %s\n", filename);
LINFO(curlun, "using only first %d blocks\n",
(int) num_sectors);
}
```
that limits the maximum size of the ISO supported.. The reason for this is that
the CD Audio format specifies locations of the media in the format `Minutes-Seconds-Frames`.
or`MSF`. The `Minutes` field is specifies on 8-bits and is therefore limited to a
maximum value of 255, and there are 75 frames of 2048 bytes per seconds. The maximum
ISO size is therefore `255*60*75*2048` or 2.189 GB.
DVDROM devices can clearly support larger files than this and so we need to be able to
remove this restriction to support larger files..
## The store_cdrom_address function
The function in `storage_common.c` where the `Minutes` field is actually written
somewhere is `store_cdrom_address`. This function is called from the functions
`do_read_toc` and `do_read_header` in `f_mass_storage.c`.
[The SCSI MMC-3 standard](
http://www.13thmonkey.org/documentation/SCSI/mmc3r10g.pdf)
defines the commands that the USB mass storage gadget needs to implement to simulate
a CDROM or DVDROM. The READ_TOC command seems to only be valid for MSF formats.
Examining the section of the standard for the READ_TOC command, the standard says
>"If the Track/Session Number field is not valid for the currently installed medium,
the command shall be terminated with CHECK CONDITION status and SK/ASC/ASCQ shall
be set to ILLEGAL REQUEST/INVALID FIELD IN CDB."
Therefore, the READ_TOC command should be rejected for files larger than 2,18GB. This
won't stop the media from working it will only prevent the audio IOCTL from functioning
on the media.
For the READ_HEADER command the situation seems to be slightly different. This
command is no longer part of the MMC-3 standard, but was part of the older
[SCSI-3 standard](
http://www.13thmonkey.org/documentation/SCSI/x3_304_1997.pdf). If
the newer commands `GET_CONFIGURATION` and `READ_DISC_INFORMATION` are implemented
this this command seems never to be called. To avoid issues the solution seems
to be if MSF format is requested and the number of sectors exceeds the maximum CD
size, to return an `INVALID FIELD in CDB` error.
We can take the implementation of the `GET_CONFIGURATION` and `READ_DISC_INFORMATION`
from a [previous patch to the mass storage gadget](
https://linuxehacking.ovh/2013/07/12/how-to-emulatore-dvd-rom-hardware-usb/).
For the 5.15 kernel, this gives a final patch
```diff
--- drivers/usb/gadet/function/f_mass_storage.c.old
2022-11-10 17:15:43.000000000 +0000
+++ drivers/usb/gadet/function/f_mass_storage.c
2023-04-11 17:19:44.830000399 +0000
@@ -200,7 +200,7 @@
/*------------------------------------------------------------------------*/
#define FSG_DRIVER_DESC
"Mass Storage Function"
-#define FSG_DRIVER_VERSION
"2009/09/11"
+#define FSG_DRIVER_VERSION
"2023/04/05"
static const char fsg_string_interface[] = "Mass Storage";
@@ -1139,7 +1139,8 @@
curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
return -EINVAL;
}
-
if (lba >= curlun->num_sectors) {
+
if (lba >= curlun->num_sectors ||
+
(msf && curlun->num_sectors > CD_MAX_SECTORS)) {
curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
return -EINVAL;
}
@@ -1150,6 +1151,61 @@
return 8;
}
+
+static int do_read_disc_information(struct fsg_common* common, struct fsg_buffhd * bh)
+{
+
struct fsg_lun *curlun = common->curlun;
+
if (common->cmnd[1] & ~0x02) { /* Mask away MSF */
+
curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+
return -EINVAL;
+
}
+
u8* outbuf = (u8*)bh->buf;
+
memset(outbuf,0,34);
+
outbuf[1] = 32;
+
outbuf[2] = 0xe; /* last session complete, disc finalized */
+
outbuf[3] = 1; /* first track on disc */
+
outbuf[4] = 1; /* # of sessions */
+
outbuf[5] = 1; /* first track of last session */
+
outbuf[6] = 1; /* last track of last session */
+
outbuf[7] = 0x20; /* unrestricted use */
+
outbuf[8] = 0x00; /* CD-ROM or DVD-ROM */
+
return 34;
+}
+
+static int do_get_configuration(struct fsg_common *common, struct fsg_buffhd *bh)
+{
+
struct fsg_lun *curlun = common->curlun;
+
if (common->cmnd[1] & ~0x02) { /* Mask away MSF */
+
curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+
return -EINVAL;
+
}
+
u8* buf = (u8*)bh->buf;
+
int cur;
+
if ( curlun->num_sectors > CD_MAX_SECTORS )
+
cur = MMC_PROFILE_DVD_ROM;
+
else
+
cur = MMC_PROFILE_CD_ROM;
+
memset(buf,0,40);
+
put_unaligned_be32(36,&buf[0]);
+
put_unaligned_be16(cur,&buf[6]);
+
buf[10] = 0x03;
+
buf[11] = 8;
+
put_unaligned_be16(MMC_PROFILE_DVD_ROM,&buf[12]);
+
buf[14] = ( cur == MMC_PROFILE_DVD_ROM );
+
put_unaligned_be16(MMC_PROFILE_CD_ROM,&buf[16]);
+
buf[18] = ( cur == MMC_PROFILE_CD_ROM );
+
put_unaligned_be16(1,&buf[20]);
+
buf[22] = 0x08 | 0x03;
+
buf[23] = 8;
+
put_unaligned_be32(1,&buf[24]);
+
buf[28] = 1;
+
put_unaligned_be16(3,&buf[32]);
+
buf[34] = 0x08 | 0x3;
+
buf[35] = 4;
+
buf[36] = 0x39;
+
return 40;
+}
+
static int do_read_toc(struct fsg_common *common, struct fsg_buffhd *bh)
{
struct fsg_lun
*curlun = common->curlun;
@@ -1158,7 +1214,8 @@
u8
*buf = (u8 *)bh->buf;
if ((common->cmnd[1] & ~0x02) != 0 ||
/* Mask away MSF */
-
start_track > 1) {
+
start_track > 1 ||
+
curlun->num_sectors > CD_MAX_SECTORS) {
curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
return -EINVAL;
}
@@ -1997,6 +2054,20 @@
reply = do_write(common);
break;
+
case 0x51://READ_DISC_INFORMATION
+
common->data_size_from_cmnd = 0;
+
if (!common->curlun || !common->curlun->cdrom)
+
goto unknown_cmnd;
+
reply = do_read_disc_information(common,bh);
+
break;
+
+
case 0x46://GET_CONFIGURATION
+
common->data_size_from_cmnd = 0;
+
if (!common->curlun || !common->curlun->cdrom)
+
goto unknown_cmnd;
+
reply = do_get_configuration(common,bh);
+
break;
+
/*
* Some mandatory commands that we recognize but don't implement.
* They don't mean much in this setting. It's left as an exercise
--- drivers/usb/gadet/function/storage_common.h.old
2023-04-11 16:41:27.820000125 +0000
+++ drivers/usb/gadet/function/storage_common.h
2023-04-11 15:24:16.320000480 +0000
@@ -85,6 +85,42 @@
#define SS_WRITE_ERROR
0x030c02
#define SS_WRITE_PROTECTED
0x072700
+#define CD_MINS 80 /* max. minutes per CD */
+#define CD_SECS 60 /* seconds per minute */
+#define CD_FRAMES 75 /* frames per second */
+#define CD_FRAMESIZE 2048 /* bytes per frame, "cooked" mode */
+#define CD_MAX_BYTES (CD_MINS * CD_SECS * CD_FRAMES * CD_FRAMESIZE)
+#define CD_MAX_SECTORS (CD_MAX_BYTES / 512)
+
+#define MMC_PROFILE_NONE 0x0000
+#define MMC_PROFILE_CD_ROM 0x0008
+#define MMC_PROFILE_CD_R 0x0009
+#define MMC_PROFILE_CD_RW 0x000A
+#define MMC_PROFILE_DVD_ROM 0x0010
+#define MMC_PROFILE_DVD_R_SR 0x0011
+#define MMC_PROFILE_DVD_RAM 0x0012
+#define MMC_PROFILE_DVD_RW_RO 0x0013
+#define MMC_PROFILE_DVD_RW_SR 0x0014
+#define MMC_PROFILE_DVD_R_DL_SR 0x0015
+#define MMC_PROFILE_DVD_R_DL_JR 0x0016
+#define MMC_PROFILE_DVD_RW_DL 0x0017
+#define MMC_PROFILE_DVD_DDR 0x0018
+#define MMC_PROFILE_DVD_PLUS_RW 0x001A
+#define MMC_PROFILE_DVD_PLUS_R 0x001B
+#define MMC_PROFILE_DVD_PLUS_RW_DL 0x002A
+#define MMC_PROFILE_DVD_PLUS_R_DL 0x002B
+#define MMC_PROFILE_BD_ROM 0x0040
+#define MMC_PROFILE_BD_R_SRM 0x0041
+#define MMC_PROFILE_BD_R_RRM 0x0042
+#define MMC_PROFILE_BD_RE 0x0043
+#define MMC_PROFILE_HDDVD_ROM 0x0050
+#define MMC_PROFILE_HDDVD_R 0x0051
+#define MMC_PROFILE_HDDVD_RAM 0x0052
+#define MMC_PROFILE_HDDVD_RW 0x0053
+#define MMC_PROFILE_HDDVD_R_DL 0x0058
+#define MMC_PROFILE_HDDVD_RW_DL 0x005A
+#define MMC_PROFILE_INVALID 0xFFFF
+
#define SK(x)
((u8) ((x) >> 16))
/* Sense Key byte, etc. */
#define ASC(x)
((u8) ((x) >> 8))
#define ASCQ(x)
((u8) (x))
--- drivers/usb/gadet/function/storage_common.c.old
2023-04-11 17:53:14.720000638 +0000
+++ drivers/usb/gadet/function/storage_common.c
2023-04-11 16:42:34.770000133 +0000
@@ -243,12 +243,6 @@
min_sectors = 1;
if (curlun->cdrom) {
min_sectors = 300;
/* Smallest track is 300 frames */
-
if (num_sectors >= 256*60*75) {
-
num_sectors = 256*60*75 - 1;
-
LINFO(curlun, "file too big: %s\n", filename);
-
LINFO(curlun, "using only first %d blocks\n",
-
(int) num_sectors);
-
}
}
if (num_sectors < min_sectors) {
LINFO(curlun, "file too small: %s\n", filename);
@@ -303,6 +297,8 @@
addr /= 75;
dest[2] = addr % 60;
/* Seconds */
addr /= 60;
+
if (addr > 255)
+
printk("store_cdrom_address: Addr overflow");
dest[1] = addr;
/* Minutes */
dest[0] = 0;
/* Reserved */
} else {
```
In the following discussion we assume that this patch is saved in the file
`dvdrom.patch`. As this point we need to rebuild the `usb_f_mass_storage.ko`
kernel module. The modern way to do this would be with [DKMS](
https://www.collabora.com/news-and-blog/blog/2021/05/05/quick-hack-patching-kernel-module-using-dkms/),
though the USB armory kernel headers don't include all of the files
necessary to allow this.
We are therefore required to recreate a minimal kernel source tree like for the
USB Armory, with exactly the same configuration and rebuild the module in this
source tree. If the script
```shell
! /bin/bash
src="."
patch="$(dirname $0)/dvdrom.patch"
kernelver=$(uname -r)
major=$(echo $kernelver | cut -d. -f1)
minor=$(echo $kernelver | cut -d. -f2)
subver=$(echo $kernelver | cut -d. -f3 | cut -d- -f1)
version="$major.$minor" # recombine as needed
# Test if sufficent space available for the kernel build
[ "$(df --block-size=1M /mnt2 | tail -1 | xargs | cut -d' ' -f4)" -le 1500 ] \
&& { echo "Insufficient space for the build"; exit 1; }
if [ ! -f "$src/linux-$version.$subver.tar.xz" ]; then
echo "Downloading kernel source $version.$subver for $kernelver"
wget -P "$src"
https://mirrors.edge.kernel.org/pub/linux/kernel/v$major.x/linux-$version.$subver.tar.xzfi
if [ ! -d "$src/linux-$version.$subver" ]; then
echo "Extracting original kernel source for $kernelver"
tar -xJf "$src/linux-$version.$subver.tar.xz"
echo "Patching usb_f_mass_storage driver"
(cd "$src/linux-$version.$subver/drivers/usb/gadget/function"; patch) < $patch
fi
if [ ! -f "$src/linux-$version.$subver/.config" ]; then
echo "Downloading USB Armory kernel config usbarmory_linux-$version.defconfig"
wget
https://raw.githubusercontent.com/usbarmory/usbarmory/master/software/kernel_conf/usbarmory_linux-$version.defconfig -O "$src/linux-$version.$subver/.config"
echo "Preparing kernel for the build"
make -C "$src/linux-$version.$subver" olddefconfig
make -C "$src/linux-$version.$subver" prepare
make -C "$src/linux-$version.$subver" modules_prepare
fi
# Send build to log because we don't care about unresolved symbols
echo "Building usb_f_mass_storage.ko"
rm -f "$src/linux-$version.$subver/drivers/usb/gadget/function/usb_f_mass_storage.ko"
make -C "$src/linux-$version.$subver" M=drivers/usb/gadget/function usb_f_mass_storage.ko > "$src/build.log" 2>&1
# Test if build sucessfull
[ -f "$src/linux-$version.$subver/drivers/usb/gadget/function/usb_f_mass_storage.ko" ] \
|| { echo "### Build failed, see $src/build.log"; exit 1; }
# Package module for later use
cp "$src/linux-$version.$subver/drivers/usb/gadget/function/usb_f_mass_storage.ko" "$src/usb_f_mass_storage-$version.$subver.ko"
# Remove everything that was used in the build of the module
rm -fr "$src/linux-$version.$subver"
```
is saved as `script.sh` in the same directory as `dvdrom.patch`, then the
`usb_f_mass_storage.ko`kernel module can be rebuild as
```
chmod +x ./script.sh
./script.sh
```
To replace the existing module, you can then do
```
sudo cp usb_f_mass_storage-$(uname -r | cut -d- -f1).ko /lib/modules/$(uname -r)/kernel/drivers/usb/gadget/function/usb_f_mass_storage.ko
``̀
A reboot of the USB Armory is then necessary for this change to be taken into account.
Note that as the file `Module.symvers` of the kernel is only built for a full kernel build
and the USB Armory headers doesn't include this file, the modprobe on the usb_f_mass_storage
module will give a warning in the logs
```
[ XX.XXXXXX] usb_f_mass_storage: no symbol version for module_layout
```
This error can be ignored.
The above code is run during a standard build for the USB armory and can be called
specifically with the command
```
make module
```
Note that 1,4GB is needed in the home directory of the USB armory, otherwise something
like
```
make module /mnt
```
allows the module to be build elsewhere.