[PATCH 3/6] fms: Add TOC/PMA/ATIP DVD-ROM capabilities

From: Igor Kononenko
Date: Sat Jun 26 2021 - 17:19:08 EST


The DVD-ROM required the SCSI 6.25 READ TOC/PMA/ATIP Command formats:
* Response Format 0000b: Formatted TOC
* Response Format 0001b: Multi-session Information
(MMC-6 Specification).

This patch adds an implementation of that described above formats.

Signed-off-by: Igor Kononenko <i.kononenko@xxxxxxxxx>
---
drivers/usb/gadget/function/f_mass_storage.c | 99 +++++++++--
drivers/usb/gadget/function/storage_common.c | 10 +-
include/uapi/linux/cdrom.h | 177 +++++++++++++++++++
3 files changed, 261 insertions(+), 25 deletions(-)

diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c
index c3fddee21b53..4865937799aa 100644
--- a/drivers/usb/gadget/function/f_mass_storage.c
+++ b/drivers/usb/gadget/function/f_mass_storage.c
@@ -223,6 +223,7 @@
#include <linux/usb/composite.h>

#include <linux/nospec.h>
+#include <linux/cdrom.h>

#include "configfs.h"

@@ -1319,29 +1320,93 @@ static int do_read_header(struct fsg_common *common, struct fsg_buffhd *bh)

static int do_read_toc(struct fsg_common *common, struct fsg_buffhd *bh)
{
- struct fsg_lun *curlun = common->curlun;
- int msf = common->cmnd[1] & 0x02;
- int start_track = common->cmnd[6];
- u8 *buf = (u8 *)bh->buf;
+ struct fsg_lun *curlun = common->curlun;
+ struct cdb_read_toc_pma_atip *cdb =
+ (struct cdb_read_toc_pma_atip *)common->cmnd;
+ struct read_tpa_header *header = (struct read_tpa_header *)bh->buf;
+ struct read_tpa_toc_formatted *data =
+ (struct read_tpa_toc_formatted *)((u8 *)bh->buf +
+ sizeof(*header));
+ size_t data_size = sizeof(*header);

- if ((common->cmnd[1] & ~0x02) != 0 || /* Mask away MSF */
- start_track > 1) {
+ if (cdb->format == 0) {
+ if (cdb->control == READ_TPA_CTRL_MAGIC_SESS) {
+ LDBG(curlun,
+ "The MMC-3 specifies format a control byte. Using Multi-Session info\n");
+ cdb->format = CDB_TPA_MULTI_SESS_INFO;
+ }
+ if (cdb->control == READ_TPA_CTRL_MAGIC_RAW) {
+ LDBG(curlun,
+ "The MMC-3 specifies format a control byte. Using RAW TOC\n");
+ cdb->format = CDB_TPA_RAW_TOC;
+ }
+ }
+
+ /* Currently support response format 0000b: Formatted TOC only */
+ if (cdb->format > CDB_TPA_MULTI_SESS_INFO) {
+ LDBG(curlun, "Unsupported TOC/PMA/ATIP format: %02Xh\n",
+ cdb->format);
curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
return -EINVAL;
}

- memset(buf, 0, 20);
- buf[1] = (20-2); /* TOC data length */
- buf[2] = 1; /* First track number */
- buf[3] = 1; /* Last track number */
- buf[5] = 0x16; /* Data track, copying allowed */
- buf[6] = 0x01; /* Only track is number 1 */
- store_cdrom_address(&buf[8], msf, 0);
+ /*
+ * We only support one track per disk.
+ * We also needs to indicate the number of the last track
+ */
+ if (cdb->number > 1 && cdb->number != READ_TPA_LEADOUT_TRACK) {
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }

- buf[13] = 0x16; /* Lead-out track is data */
- buf[14] = 0xAA; /* Lead-out track number */
- store_cdrom_address(&buf[16], msf, curlun->num_sectors);
- common->data_size_to_handle = 20;
+ /*
+ * MULTI-SESSIOIN information must be reported only for first track.
+ */
+ if (cdb->format == CDB_TPA_MULTI_SESS_INFO && cdb->number > 1) {
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
+
+ memset(header, 0, sizeof(*header));
+ header->n_first_stf = 1;
+ header->n_last_stf = 1;
+
+ memset(data, 0, sizeof(*data));
+ data->addr = 1;
+ data->control = TPA_SECTOR_MODE2_MIXED;
+ data->track_number = cdb->number;
+ data_size += sizeof(*data);
+
+ /*
+ * We have too case:
+ * 1) The request Track Number == 1.
+ * We shall set 2 descriptors: First Track, Lead-Out Track
+ * 2) The requested Track Number == 0xAA
+ * Only Lead-Out descriptor shall be set
+ */
+ if (cdb->number == 1) {
+ DBG(common, "Fill first track addr\n");
+ store_cdrom_address((u8 *)&data->start_addr_track, cdb->msf, 0);
+
+ data += 1; /* Add one more descriptor */
+ data_size += sizeof(*data);
+ memset(data, 0, sizeof(*data));
+ /* setting the lead-out track info. First part of data*/
+ data->addr = 1;
+ data->control = TPA_SECTOR_MODE2_MIXED;
+ data->track_number = READ_TPA_LEADOUT_TRACK;
+ }
+
+ /*
+ * Lead-out track must be set anyway.
+ * If 0xAA Track is requested - the first part of data is already set.
+ */
+ DBG(common, "Fill last track addr\n");
+ store_cdrom_address((u8 *)&data->start_addr_track,
+ cdb->msf, curlun->num_sectors);
+
+ header->length = cpu_to_be16(data_size - sizeof(header->length));
+ common->data_size_to_handle = data_size;
return 0;
}

diff --git a/drivers/usb/gadget/function/storage_common.c b/drivers/usb/gadget/function/storage_common.c
index b859a158a414..b883b8b7b05b 100644
--- a/drivers/usb/gadget/function/storage_common.c
+++ b/drivers/usb/gadget/function/storage_common.c
@@ -24,6 +24,7 @@
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/usb/composite.h>
+#include <linux/cdrom.h>

#include "storage_common.h"

@@ -295,14 +296,7 @@ void store_cdrom_address(u8 *dest, int msf, u32 addr)
{
if (msf) {
/* Convert to Minutes-Seconds-Frames */
- addr >>= 2; /* Convert to 2048-byte frames */
- addr += 2*75; /* Lead-in occupies 2 seconds */
- dest[3] = addr % 75; /* Frames */
- addr /= 75;
- dest[2] = addr % 60; /* Seconds */
- addr /= 60;
- dest[1] = addr; /* Minutes */
- dest[0] = 0; /* Reserved */
+ lba_to_msf(addr, &dest[1], &dest[2], &dest[3]);
} else {
/* Absolute sector */
put_unaligned_be32(addr, dest);
diff --git a/include/uapi/linux/cdrom.h b/include/uapi/linux/cdrom.h
index 6c34f6e2f1f7..1d7b4333c3aa 100644
--- a/include/uapi/linux/cdrom.h
+++ b/include/uapi/linux/cdrom.h
@@ -947,4 +947,181 @@ struct rm_feature_desc {
__u8 reserved4;
};

+/**
+ * The READ TOC/PMA/ATIP format field values
+ */
+enum cdb_read_tpa_format {
+ /**
+ * The Track/Session Number field specifies starting track number
+ * for which the data is returned. For multi-session discs, TOC
+ * data is returned for all sessions. Track number AAh is reported
+ * only for the Lead-out area of the last complete session.
+ */
+ CDB_TPA_FORMATTED_TOC,
+ /**
+ * This format returns the first complete session number, last
+ * complete session number and last complete session starting address.
+ * In this format, the Track/Session Number field is reserved and
+ * should be set to 00h.
+ * NOTE: This format provides the Host access to the last closed
+ * session starting address quickly.
+ */
+ CDB_TPA_MULTI_SESS_INFO,
+ /**
+ * This format returns all Q sub-code data in the Lead-In (TOC) areas
+ * starting from a session number as specified in the Number
+ * Track/Session Number field.
+ * In this mode, the Drive shall support Q Sub-channel POINT field
+ * value of A0h, A1h, A2h, Track numbers, B0h, B1h, B2h, B3h, B4h, C0h,
+ * and C1h.
+ * There is no defined LBA addressing and MSF bit shall be set to one.
+ */
+ CDB_TPA_RAW_TOC,
+ /**
+ * This format returns Q sub-channel data in the PMA area. In this
+ * format, the Track/Session Number field is reserved and shall be
+ * set to 00h. There is no defined LBA addressing and MSF bit
+ * shall be set to one.
+ */
+ CDB_TPA_PMA,
+ /**
+ * This format returns ATIP data. In this format, the Track/Session
+ * Number field is reserved and shall be set to 00h. There is no
+ * defined LBA addressing and MSF bit shall be set to one.
+ */
+ CDB_TPA_ATIP,
+ /**
+ * This format returns CD-TEXT information that is recorded in the
+ * Lead-in area as R-W Sub-channel Data.
+ */
+ CDB_TPA_CD_TEXT,
+};
+
+#define TPA_SECTOR_MODE0 (0x00)
+#define TPA_SECTOR_AUDIO (0x01)
+#define TPA_SECTOR_MODE1 (0x02)
+#define TPA_SECTOR_MODE2 (0x03)
+#define TPA_SECTOR_MODE2_FORM1 (0x04)
+#define TPA_SECTOR_MODE2_FORM2 (0x05)
+#define TPA_SECTOR_MODE2_MIXED (TPA_SECTOR_MODE1 | TPA_SECTOR_MODE2_FORM1)
+#define TPA_SECTOR_RAW (0x07)
+#define TPA_SECTOR_RAW_SCRAMBLED (0x08)
+
+/**
+ * @brief The READ TOC/PMA/ATIP CDB (43h)
+ * The READ TOC/PMA/ATIP command requests that the Drive read data from a
+ * Table of Contents, the Program Memory Area (PMA), or the Absolute Time
+ * in Pre-Grove (ATIP) from CD media, format according to CDB parameters
+ * and transfer the result to the Host.
+ */
+struct cdb_read_toc_pma_atip {
+ __u8 code;
+
+#if defined(__BIG_ENDIAN_BITFIELD)
+ __u8 reserved1 : 6;
+ /**
+ * When MSF is set to zero, the address fields in some returned data
+ * formats shall be in LBA form. When MSF is set to one, the address
+ * fields in some returned data formats shall be in MSF form
+ */
+ __u8 msf : 1;
+ __u8 reserved2 : 1;
+#elif defined(__LITTLE_ENDIAN_BITFIELD)
+ __u8 reserved2 : 1;
+ __u8 msf : 1;
+ __u8 reserved1 : 6;
+#endif
+
+#if defined(__BIG_ENDIAN_BITFIELD)
+ __u8 reserved3 : 4;
+ /**
+ * The Format field is used to select specific returned data format
+ * according to @enum cdb_read_tpa_format
+ */
+ __u8 format : 4;
+#elif defined(__LITTLE_ENDIAN_BITFIELD)
+ __u8 format : 4;
+ __u8 reserved3 : 4;
+#endif
+
+ __u8 reserved4[3];
+ /**
+ * The Track/Session Number field provides a method to restrict the
+ * returned of some data formats to a specific session or a track range
+ */
+ __u8 number;
+
+ /**
+ * The Allocation Length field specifies the maximum number of bytes that
+ * may be returned by the Drive.
+ * An Allocation Length field of zero shall not be considered an error
+ */
+ __be16 length;
+
+ __u8 control;
+} __packed;
+
+#define READ_TPA_LEADOUT_TRACK (0xAAU)
+/*
+ * Control magic byte
+ * Some legacy media recorder implementations set the control byte,
+ * helping determine the relevant TOC/PMA/ATIP formats.
+ * We should support this as well.
+ */
+#define READ_TPA_CTRL_MAGIC_SESS (0x40U)
+#define READ_TPA_CTRL_MAGIC_RAW (0x80U)
+
+/**
+ * @brief READ TOC/PMA/ATIP Data list header
+ * The response data list shows the general description of the response data
+ * to the Read TOC/PMA/ATIP command.
+ */
+struct read_tpa_header {
+ __be16 length;
+ /* First Track/Session/Reserved Field */
+ __u8 n_first_stf;
+ /* Last Track/Session/Reserved Field */
+ __u8 n_last_stf;
+} __packed;
+
+/**
+ * @brief Response Format 0000b: Formatted TOC
+ * The response data consist of four header bytes and zero or more track
+ * descriptors.
+ */
+struct read_tpa_toc_formatted {
+ __u8 reserved1;
+#if defined(__BIG_ENDIAN_BITFIELD)
+ /**
+ * The ADR field gives the type of information encoded in the Q Sub-channel
+ * of the block where this TOC entry was found.
+ */
+ __u8 addr : 4;
+ /**
+ * The CONTROL Field indicates the attributes of the track.
+ */
+ __u8 control : 4;
+#elif defined(__LITTLE_ENDIAN_BITFIELD)
+ __u8 control : 4;
+ __u8 addr : 4;
+#endif
+ /**
+ * The Track Start Address contains the address of the first block with user
+ * information for that track number as read from the Table of Contents.
+ * A MSF bit of zero indicates that the Track Start Address field shall contain
+ * a logical block address.
+ * A MSF bit of one indicates the Logical Block Address field shall contain a
+ * MSF address
+ */
+ __u8 track_number;
+ __u8 reserved2;
+ /**
+ * The Track Number field indicates the track number for that the data in the
+ * TOC track descriptor is valid. A track number of READ_TPA_LEADOUT_TRACK
+ * indicates that the track descriptor is for the start of the Lead-out area.
+ */
+ __be32 start_addr_track;
+} __packed;
+
+
#endif /* _UAPI_LINUX_CDROM_H */
--
2.32.0