extmod: Factor out block-device struct to make independent of fatfs.
This commit is contained in:
parent
ece4e21a55
commit
9aabb6c01b
20
extmod/vfs.h
20
extmod/vfs.h
|
@ -38,6 +38,12 @@
|
|||
#define MP_S_IFDIR (0x4000)
|
||||
#define MP_S_IFREG (0x8000)
|
||||
|
||||
// these are the values for mp_vfs_blockdev_t.flags
|
||||
#define MP_BLOCKDEV_FLAG_NATIVE (0x0001) // readblocks[2]/writeblocks[2] contain native func
|
||||
#define MP_BLOCKDEV_FLAG_FREE_OBJ (0x0002) // fs_user_mount_t obj should be freed on umount
|
||||
#define MP_BLOCKDEV_FLAG_HAVE_IOCTL (0x0004) // new protocol with ioctl
|
||||
#define MP_BLOCKDEV_FLAG_NO_FILESYSTEM (0x0008) // the block device has no filesystem on it
|
||||
|
||||
// constants for block protocol ioctl
|
||||
#define BP_IOCTL_INIT (1)
|
||||
#define BP_IOCTL_DEINIT (2)
|
||||
|
@ -50,6 +56,20 @@ typedef struct _mp_vfs_proto_t {
|
|||
mp_import_stat_t (*import_stat)(void *self, const char *path);
|
||||
} mp_vfs_proto_t;
|
||||
|
||||
typedef struct _mp_vfs_blockdev_t {
|
||||
uint16_t flags;
|
||||
mp_obj_t readblocks[4];
|
||||
mp_obj_t writeblocks[4];
|
||||
// new protocol uses just ioctl, old uses sync (optional) and count
|
||||
union {
|
||||
mp_obj_t ioctl[4];
|
||||
struct {
|
||||
mp_obj_t sync[2];
|
||||
mp_obj_t count[2];
|
||||
} old;
|
||||
} u;
|
||||
} mp_vfs_blockdev_t;
|
||||
|
||||
typedef struct _mp_vfs_mount_t {
|
||||
const char *str; // mount point with leading /
|
||||
size_t len;
|
||||
|
|
|
@ -68,27 +68,27 @@ STATIC mp_obj_t fat_vfs_make_new(const mp_obj_type_t *type, size_t n_args, size_
|
|||
// create new object
|
||||
fs_user_mount_t *vfs = m_new_obj(fs_user_mount_t);
|
||||
vfs->base.type = type;
|
||||
vfs->flags = FSUSER_FREE_OBJ;
|
||||
vfs->blockdev.flags = MP_BLOCKDEV_FLAG_FREE_OBJ;
|
||||
vfs->fatfs.drv = vfs;
|
||||
|
||||
// load block protocol methods
|
||||
mp_load_method(args[0], MP_QSTR_readblocks, vfs->readblocks);
|
||||
mp_load_method_maybe(args[0], MP_QSTR_writeblocks, vfs->writeblocks);
|
||||
mp_load_method_maybe(args[0], MP_QSTR_ioctl, vfs->u.ioctl);
|
||||
if (vfs->u.ioctl[0] != MP_OBJ_NULL) {
|
||||
mp_load_method(args[0], MP_QSTR_readblocks, vfs->blockdev.readblocks);
|
||||
mp_load_method_maybe(args[0], MP_QSTR_writeblocks, vfs->blockdev.writeblocks);
|
||||
mp_load_method_maybe(args[0], MP_QSTR_ioctl, vfs->blockdev.u.ioctl);
|
||||
if (vfs->blockdev.u.ioctl[0] != MP_OBJ_NULL) {
|
||||
// device supports new block protocol, so indicate it
|
||||
vfs->flags |= FSUSER_HAVE_IOCTL;
|
||||
vfs->blockdev.flags |= MP_BLOCKDEV_FLAG_HAVE_IOCTL;
|
||||
} else {
|
||||
// no ioctl method, so assume the device uses the old block protocol
|
||||
mp_load_method_maybe(args[0], MP_QSTR_sync, vfs->u.old.sync);
|
||||
mp_load_method(args[0], MP_QSTR_count, vfs->u.old.count);
|
||||
mp_load_method_maybe(args[0], MP_QSTR_sync, vfs->blockdev.u.old.sync);
|
||||
mp_load_method(args[0], MP_QSTR_count, vfs->blockdev.u.old.count);
|
||||
}
|
||||
|
||||
// mount the block device so the VFS methods can be used
|
||||
FRESULT res = f_mount(&vfs->fatfs);
|
||||
if (res == FR_NO_FILESYSTEM) {
|
||||
// don't error out if no filesystem, to let mkfs()/mount() create one if wanted
|
||||
vfs->flags |= FSUSER_NO_FILESYSTEM;
|
||||
vfs->blockdev.flags |= MP_BLOCKDEV_FLAG_NO_FILESYSTEM;
|
||||
} else if (res != FR_OK) {
|
||||
mp_raise_OSError(fresult_to_errno_table[res]);
|
||||
}
|
||||
|
@ -380,11 +380,11 @@ STATIC mp_obj_t vfs_fat_mount(mp_obj_t self_in, mp_obj_t readonly, mp_obj_t mkfs
|
|||
// 1. readonly=True keyword argument
|
||||
// 2. nonexistent writeblocks method (then writeblocks[0] == MP_OBJ_NULL already)
|
||||
if (mp_obj_is_true(readonly)) {
|
||||
self->writeblocks[0] = MP_OBJ_NULL;
|
||||
self->blockdev.writeblocks[0] = MP_OBJ_NULL;
|
||||
}
|
||||
|
||||
// check if we need to make the filesystem
|
||||
FRESULT res = (self->flags & FSUSER_NO_FILESYSTEM) ? FR_NO_FILESYSTEM : FR_OK;
|
||||
FRESULT res = (self->blockdev.flags & MP_BLOCKDEV_FLAG_NO_FILESYSTEM) ? FR_NO_FILESYSTEM : FR_OK;
|
||||
if (res == FR_NO_FILESYSTEM && mp_obj_is_true(mkfs)) {
|
||||
uint8_t working_buf[FF_MAX_SS];
|
||||
res = f_mkfs(&self->fatfs, FM_FAT | FM_SFD, 0, working_buf, sizeof(working_buf));
|
||||
|
@ -392,7 +392,7 @@ STATIC mp_obj_t vfs_fat_mount(mp_obj_t self_in, mp_obj_t readonly, mp_obj_t mkfs
|
|||
if (res != FR_OK) {
|
||||
mp_raise_OSError(fresult_to_errno_table[res]);
|
||||
}
|
||||
self->flags &= ~FSUSER_NO_FILESYSTEM;
|
||||
self->blockdev.flags &= ~MP_BLOCKDEV_FLAG_NO_FILESYSTEM;
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
|
|
@ -26,30 +26,13 @@
|
|||
#ifndef MICROPY_INCLUDED_EXTMOD_VFS_FAT_H
|
||||
#define MICROPY_INCLUDED_EXTMOD_VFS_FAT_H
|
||||
|
||||
#include "py/lexer.h"
|
||||
#include "py/obj.h"
|
||||
#include "lib/oofatfs/ff.h"
|
||||
#include "extmod/vfs.h"
|
||||
|
||||
// these are the values for fs_user_mount_t.flags
|
||||
#define FSUSER_NATIVE (0x0001) // readblocks[2]/writeblocks[2] contain native func
|
||||
#define FSUSER_FREE_OBJ (0x0002) // fs_user_mount_t obj should be freed on umount
|
||||
#define FSUSER_HAVE_IOCTL (0x0004) // new protocol with ioctl
|
||||
#define FSUSER_NO_FILESYSTEM (0x0008) // the block device has no filesystem on it
|
||||
|
||||
typedef struct _fs_user_mount_t {
|
||||
mp_obj_base_t base;
|
||||
uint16_t flags;
|
||||
mp_obj_t readblocks[4];
|
||||
mp_obj_t writeblocks[4];
|
||||
// new protocol uses just ioctl, old uses sync (optional) and count
|
||||
union {
|
||||
mp_obj_t ioctl[4];
|
||||
struct {
|
||||
mp_obj_t sync[2];
|
||||
mp_obj_t count[2];
|
||||
} old;
|
||||
} u;
|
||||
mp_vfs_blockdev_t blockdev;
|
||||
FATFS fatfs;
|
||||
} fs_user_mount_t;
|
||||
|
||||
|
|
|
@ -69,16 +69,16 @@ DRESULT disk_read (
|
|||
return RES_PARERR;
|
||||
}
|
||||
|
||||
if (vfs->flags & FSUSER_NATIVE) {
|
||||
mp_uint_t (*f)(uint8_t*, uint32_t, uint32_t) = (void*)(uintptr_t)vfs->readblocks[2];
|
||||
if (vfs->blockdev.flags & MP_BLOCKDEV_FLAG_NATIVE) {
|
||||
mp_uint_t (*f)(uint8_t*, uint32_t, uint32_t) = (void*)(uintptr_t)vfs->blockdev.readblocks[2];
|
||||
if (f(buff, sector, count) != 0) {
|
||||
return RES_ERROR;
|
||||
}
|
||||
} else {
|
||||
mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, count * SECSIZE(&vfs->fatfs), buff};
|
||||
vfs->readblocks[2] = MP_OBJ_NEW_SMALL_INT(sector);
|
||||
vfs->readblocks[3] = MP_OBJ_FROM_PTR(&ar);
|
||||
mp_call_method_n_kw(2, 0, vfs->readblocks);
|
||||
vfs->blockdev.readblocks[2] = MP_OBJ_NEW_SMALL_INT(sector);
|
||||
vfs->blockdev.readblocks[3] = MP_OBJ_FROM_PTR(&ar);
|
||||
mp_call_method_n_kw(2, 0, vfs->blockdev.readblocks);
|
||||
// TODO handle error return
|
||||
}
|
||||
|
||||
|
@ -101,21 +101,21 @@ DRESULT disk_write (
|
|||
return RES_PARERR;
|
||||
}
|
||||
|
||||
if (vfs->writeblocks[0] == MP_OBJ_NULL) {
|
||||
if (vfs->blockdev.writeblocks[0] == MP_OBJ_NULL) {
|
||||
// read-only block device
|
||||
return RES_WRPRT;
|
||||
}
|
||||
|
||||
if (vfs->flags & FSUSER_NATIVE) {
|
||||
mp_uint_t (*f)(const uint8_t*, uint32_t, uint32_t) = (void*)(uintptr_t)vfs->writeblocks[2];
|
||||
if (vfs->blockdev.flags & MP_BLOCKDEV_FLAG_NATIVE) {
|
||||
mp_uint_t (*f)(const uint8_t*, uint32_t, uint32_t) = (void*)(uintptr_t)vfs->blockdev.writeblocks[2];
|
||||
if (f(buff, sector, count) != 0) {
|
||||
return RES_ERROR;
|
||||
}
|
||||
} else {
|
||||
mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, count * SECSIZE(&vfs->fatfs), (void*)buff};
|
||||
vfs->writeblocks[2] = MP_OBJ_NEW_SMALL_INT(sector);
|
||||
vfs->writeblocks[3] = MP_OBJ_FROM_PTR(&ar);
|
||||
mp_call_method_n_kw(2, 0, vfs->writeblocks);
|
||||
vfs->blockdev.writeblocks[2] = MP_OBJ_NEW_SMALL_INT(sector);
|
||||
vfs->blockdev.writeblocks[3] = MP_OBJ_FROM_PTR(&ar);
|
||||
mp_call_method_n_kw(2, 0, vfs->blockdev.writeblocks);
|
||||
// TODO handle error return
|
||||
}
|
||||
|
||||
|
@ -140,7 +140,7 @@ DRESULT disk_ioctl (
|
|||
|
||||
// First part: call the relevant method of the underlying block device
|
||||
mp_obj_t ret = mp_const_none;
|
||||
if (vfs->flags & FSUSER_HAVE_IOCTL) {
|
||||
if (vfs->blockdev.flags & MP_BLOCKDEV_FLAG_HAVE_IOCTL) {
|
||||
// new protocol with ioctl
|
||||
static const uint8_t op_map[8] = {
|
||||
[CTRL_SYNC] = BP_IOCTL_SYNC,
|
||||
|
@ -150,21 +150,21 @@ DRESULT disk_ioctl (
|
|||
};
|
||||
uint8_t bp_op = op_map[cmd & 7];
|
||||
if (bp_op != 0) {
|
||||
vfs->u.ioctl[2] = MP_OBJ_NEW_SMALL_INT(bp_op);
|
||||
vfs->u.ioctl[3] = MP_OBJ_NEW_SMALL_INT(0); // unused
|
||||
ret = mp_call_method_n_kw(2, 0, vfs->u.ioctl);
|
||||
vfs->blockdev.u.ioctl[2] = MP_OBJ_NEW_SMALL_INT(bp_op);
|
||||
vfs->blockdev.u.ioctl[3] = MP_OBJ_NEW_SMALL_INT(0); // unused
|
||||
ret = mp_call_method_n_kw(2, 0, vfs->blockdev.u.ioctl);
|
||||
}
|
||||
} else {
|
||||
// old protocol with sync and count
|
||||
switch (cmd) {
|
||||
case CTRL_SYNC:
|
||||
if (vfs->u.old.sync[0] != MP_OBJ_NULL) {
|
||||
mp_call_method_n_kw(0, 0, vfs->u.old.sync);
|
||||
if (vfs->blockdev.u.old.sync[0] != MP_OBJ_NULL) {
|
||||
mp_call_method_n_kw(0, 0, vfs->blockdev.u.old.sync);
|
||||
}
|
||||
break;
|
||||
|
||||
case GET_SECTOR_COUNT:
|
||||
ret = mp_call_method_n_kw(0, 0, vfs->u.old.count);
|
||||
ret = mp_call_method_n_kw(0, 0, vfs->blockdev.u.old.count);
|
||||
break;
|
||||
|
||||
case GET_SECTOR_SIZE:
|
||||
|
@ -211,7 +211,7 @@ DRESULT disk_ioctl (
|
|||
if (ret != mp_const_none && MP_OBJ_SMALL_INT_VALUE(ret) != 0) {
|
||||
// error initialising
|
||||
stat = STA_NOINIT;
|
||||
} else if (vfs->writeblocks[0] == MP_OBJ_NULL) {
|
||||
} else if (vfs->blockdev.writeblocks[0] == MP_OBJ_NULL) {
|
||||
stat = STA_PROTECT;
|
||||
} else {
|
||||
stat = 0;
|
||||
|
|
|
@ -96,14 +96,14 @@ const mp_obj_type_t pyb_flash_type = {
|
|||
|
||||
void pyb_flash_init_vfs(fs_user_mount_t *vfs) {
|
||||
vfs->base.type = &mp_fat_vfs_type;
|
||||
vfs->flags |= FSUSER_NATIVE | FSUSER_HAVE_IOCTL;
|
||||
vfs->blockdev.flags |= MP_BLOCKDEV_FLAG_NATIVE | MP_BLOCKDEV_FLAG_HAVE_IOCTL;
|
||||
vfs->fatfs.drv = vfs;
|
||||
vfs->readblocks[0] = (mp_obj_t)&pyb_flash_readblocks_obj;
|
||||
vfs->readblocks[1] = (mp_obj_t)&pyb_flash_obj;
|
||||
vfs->readblocks[2] = (mp_obj_t)sflash_disk_read; // native version
|
||||
vfs->writeblocks[0] = (mp_obj_t)&pyb_flash_writeblocks_obj;
|
||||
vfs->writeblocks[1] = (mp_obj_t)&pyb_flash_obj;
|
||||
vfs->writeblocks[2] = (mp_obj_t)sflash_disk_write; // native version
|
||||
vfs->u.ioctl[0] = (mp_obj_t)&pyb_flash_ioctl_obj;
|
||||
vfs->u.ioctl[1] = (mp_obj_t)&pyb_flash_obj;
|
||||
vfs->blockdev.readblocks[0] = (mp_obj_t)&pyb_flash_readblocks_obj;
|
||||
vfs->blockdev.readblocks[1] = (mp_obj_t)&pyb_flash_obj;
|
||||
vfs->blockdev.readblocks[2] = (mp_obj_t)sflash_disk_read; // native version
|
||||
vfs->blockdev.writeblocks[0] = (mp_obj_t)&pyb_flash_writeblocks_obj;
|
||||
vfs->blockdev.writeblocks[1] = (mp_obj_t)&pyb_flash_obj;
|
||||
vfs->blockdev.writeblocks[2] = (mp_obj_t)sflash_disk_write; // native version
|
||||
vfs->blockdev.u.ioctl[0] = (mp_obj_t)&pyb_flash_ioctl_obj;
|
||||
vfs->blockdev.u.ioctl[1] = (mp_obj_t)&pyb_flash_obj;
|
||||
}
|
||||
|
|
|
@ -300,7 +300,7 @@ STATIC void mptask_init_sflash_filesystem (void) {
|
|||
// Initialise the local flash filesystem.
|
||||
// init the vfs object
|
||||
fs_user_mount_t *vfs_fat = sflash_vfs_fat;
|
||||
vfs_fat->flags = 0;
|
||||
vfs_fat->blockdev.flags = 0;
|
||||
pyb_flash_init_vfs(vfs_fat);
|
||||
|
||||
// Create it if needed, and mount in on /flash.
|
||||
|
|
|
@ -180,7 +180,7 @@ pin_init0();
|
|||
}
|
||||
vfs->str = "/sd";
|
||||
vfs->len = 3;
|
||||
vfs->flags = FSUSER_FREE_OBJ;
|
||||
vfs->flags = MP_BLOCKDEV_FLAG_FREE_OBJ;
|
||||
sdcard_init_vfs(vfs);
|
||||
|
||||
// put the sd device in slot 1 (it will be unused at this point)
|
||||
|
|
|
@ -161,7 +161,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(pyb_main_obj, 1, pyb_main);
|
|||
MP_NOINLINE STATIC bool init_flash_fs(uint reset_mode) {
|
||||
// init the vfs object
|
||||
fs_user_mount_t *vfs_fat = &fs_user_mount_flash;
|
||||
vfs_fat->flags = 0;
|
||||
vfs_fat->blockdev.flags = 0;
|
||||
pyb_flash_init_vfs(vfs_fat);
|
||||
|
||||
// try to mount the flash
|
||||
|
@ -230,7 +230,7 @@ STATIC bool init_sdcard_fs(void) {
|
|||
if (vfs == NULL || vfs_fat == NULL) {
|
||||
break;
|
||||
}
|
||||
vfs_fat->flags = FSUSER_FREE_OBJ;
|
||||
vfs_fat->blockdev.flags = MP_BLOCKDEV_FLAG_FREE_OBJ;
|
||||
sdcard_init_vfs(vfs_fat, part_num);
|
||||
|
||||
// try to mount the partition
|
||||
|
|
|
@ -873,17 +873,17 @@ const mp_obj_type_t pyb_mmcard_type = {
|
|||
void sdcard_init_vfs(fs_user_mount_t *vfs, int part) {
|
||||
pyb_sdmmc_flags = (pyb_sdmmc_flags & PYB_SDMMC_FLAG_ACTIVE) | PYB_SDMMC_FLAG_SD; // force SD mode
|
||||
vfs->base.type = &mp_fat_vfs_type;
|
||||
vfs->flags |= FSUSER_NATIVE | FSUSER_HAVE_IOCTL;
|
||||
vfs->blockdev.flags |= MP_BLOCKDEV_FLAG_NATIVE | MP_BLOCKDEV_FLAG_HAVE_IOCTL;
|
||||
vfs->fatfs.drv = vfs;
|
||||
vfs->fatfs.part = part;
|
||||
vfs->readblocks[0] = MP_OBJ_FROM_PTR(&pyb_sdcard_readblocks_obj);
|
||||
vfs->readblocks[1] = MP_OBJ_FROM_PTR(&pyb_sdcard_obj);
|
||||
vfs->readblocks[2] = MP_OBJ_FROM_PTR(sdcard_read_blocks); // native version
|
||||
vfs->writeblocks[0] = MP_OBJ_FROM_PTR(&pyb_sdcard_writeblocks_obj);
|
||||
vfs->writeblocks[1] = MP_OBJ_FROM_PTR(&pyb_sdcard_obj);
|
||||
vfs->writeblocks[2] = MP_OBJ_FROM_PTR(sdcard_write_blocks); // native version
|
||||
vfs->u.ioctl[0] = MP_OBJ_FROM_PTR(&pyb_sdcard_ioctl_obj);
|
||||
vfs->u.ioctl[1] = MP_OBJ_FROM_PTR(&pyb_sdcard_obj);
|
||||
vfs->blockdev.readblocks[0] = MP_OBJ_FROM_PTR(&pyb_sdcard_readblocks_obj);
|
||||
vfs->blockdev.readblocks[1] = MP_OBJ_FROM_PTR(&pyb_sdcard_obj);
|
||||
vfs->blockdev.readblocks[2] = MP_OBJ_FROM_PTR(sdcard_read_blocks); // native version
|
||||
vfs->blockdev.writeblocks[0] = MP_OBJ_FROM_PTR(&pyb_sdcard_writeblocks_obj);
|
||||
vfs->blockdev.writeblocks[1] = MP_OBJ_FROM_PTR(&pyb_sdcard_obj);
|
||||
vfs->blockdev.writeblocks[2] = MP_OBJ_FROM_PTR(sdcard_write_blocks); // native version
|
||||
vfs->blockdev.u.ioctl[0] = MP_OBJ_FROM_PTR(&pyb_sdcard_ioctl_obj);
|
||||
vfs->blockdev.u.ioctl[1] = MP_OBJ_FROM_PTR(&pyb_sdcard_obj);
|
||||
}
|
||||
|
||||
#endif // MICROPY_HW_ENABLE_SDCARD || MICROPY_HW_ENABLE_MMCARD
|
||||
|
|
|
@ -290,17 +290,17 @@ const mp_obj_type_t pyb_flash_type = {
|
|||
|
||||
void pyb_flash_init_vfs(fs_user_mount_t *vfs) {
|
||||
vfs->base.type = &mp_fat_vfs_type;
|
||||
vfs->flags |= FSUSER_NATIVE | FSUSER_HAVE_IOCTL;
|
||||
vfs->blockdev.flags |= MP_BLOCKDEV_FLAG_NATIVE | MP_BLOCKDEV_FLAG_HAVE_IOCTL;
|
||||
vfs->fatfs.drv = vfs;
|
||||
vfs->fatfs.part = 1; // flash filesystem lives on first partition
|
||||
vfs->readblocks[0] = MP_OBJ_FROM_PTR(&pyb_flash_readblocks_obj);
|
||||
vfs->readblocks[1] = MP_OBJ_FROM_PTR(&pyb_flash_obj);
|
||||
vfs->readblocks[2] = MP_OBJ_FROM_PTR(storage_read_blocks); // native version
|
||||
vfs->writeblocks[0] = MP_OBJ_FROM_PTR(&pyb_flash_writeblocks_obj);
|
||||
vfs->writeblocks[1] = MP_OBJ_FROM_PTR(&pyb_flash_obj);
|
||||
vfs->writeblocks[2] = MP_OBJ_FROM_PTR(storage_write_blocks); // native version
|
||||
vfs->u.ioctl[0] = MP_OBJ_FROM_PTR(&pyb_flash_ioctl_obj);
|
||||
vfs->u.ioctl[1] = MP_OBJ_FROM_PTR(&pyb_flash_obj);
|
||||
vfs->blockdev.readblocks[0] = MP_OBJ_FROM_PTR(&pyb_flash_readblocks_obj);
|
||||
vfs->blockdev.readblocks[1] = MP_OBJ_FROM_PTR(&pyb_flash_obj);
|
||||
vfs->blockdev.readblocks[2] = MP_OBJ_FROM_PTR(storage_read_blocks); // native version
|
||||
vfs->blockdev.writeblocks[0] = MP_OBJ_FROM_PTR(&pyb_flash_writeblocks_obj);
|
||||
vfs->blockdev.writeblocks[1] = MP_OBJ_FROM_PTR(&pyb_flash_obj);
|
||||
vfs->blockdev.writeblocks[2] = MP_OBJ_FROM_PTR(storage_write_blocks); // native version
|
||||
vfs->blockdev.u.ioctl[0] = MP_OBJ_FROM_PTR(&pyb_flash_ioctl_obj);
|
||||
vfs->blockdev.u.ioctl[1] = MP_OBJ_FROM_PTR(&pyb_flash_obj);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue