* [Qemu-devel] [PATCH 0/3] Improve SD controllers emulation @ 2011-12-26 10:03 Mitsyanko Igor 2011-12-26 10:03 ` [Qemu-devel] [PATCH 1/3] vmstate: introduce calc_size VMStateField Mitsyanko Igor ` (4 more replies) 0 siblings, 5 replies; 31+ messages in thread From: Mitsyanko Igor @ 2011-12-26 10:03 UTC (permalink / raw) To: qemu-devel; +Cc: kyungmin.park, m.kozlov, jehyung.lee, d.solodkiy, e.voevodin First patch of this patch set modifies existing VMStateField interface to ease save/restore functionality implementation for device's dynamically allocated buffers. This modification is used later in second patch to implement SD card's VMStateDescription structure. Third patch adds imlementation of new device: SD host controller fully compliant with "SD host controller specification version 2.00". It also uses first patch modifications. Mitsyanko Igor (3): vmstate: introduce calc_size VMStateField hw/sd.c: add SD card save/load support hw/: Introduce spec. ver. 2.00 compliant SD host controller Makefile.target | 1 + hw/hw.h | 14 +- hw/milkymist-memcard.c | 2 + hw/sd.c | 115 +++-- hw/sd.h | 1 + hw/sdhc_ver2.c | 1579 ++++++++++++++++++++++++++++++++++++++++++++++++ hw/sdhc_ver2.h | 327 ++++++++++ savevm.c | 14 +- 8 files changed, 2004 insertions(+), 49 deletions(-) create mode 100644 hw/sdhc_ver2.c create mode 100644 hw/sdhc_ver2.h -- 1.7.4.1 ^ permalink raw reply [flat|nested] 31+ messages in thread
* [Qemu-devel] [PATCH 1/3] vmstate: introduce calc_size VMStateField 2011-12-26 10:03 [Qemu-devel] [PATCH 0/3] Improve SD controllers emulation Mitsyanko Igor @ 2011-12-26 10:03 ` Mitsyanko Igor 2011-12-26 15:20 ` Peter Maydell 2011-12-27 7:54 ` [Qemu-devel] [PATCH V2 1/3] vmstate: introduce get_bufsize entry in VMStateField Mitsyanko Igor 2011-12-26 10:03 ` [Qemu-devel] [PATCH 2/3] hw/sd.c: add SD card save/load support Mitsyanko Igor ` (3 subsequent siblings) 4 siblings, 2 replies; 31+ messages in thread From: Mitsyanko Igor @ 2011-12-26 10:03 UTC (permalink / raw) To: qemu-devel Cc: Mitsyanko Igor, e.voevodin, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee New calc_size field in VMStateField is supposed to help us easily add save/restore support of dynamically allocated buffers in device's states. There are some cases when information on size of dynamically allocated buffer is already presented in specific device's state structure, but in such a form, that can not be used with existing VMStateField interface. Currently, we either can get size from another variable in device's state as it is with VMSTATE_VBUFFER_* macros, or we can also multiply value kept in a variable by a constant with VMSTATE_BUFFER_MULTIPLY macro. If we need to perform any other action, we're forced to add additional variable with size information to device state structure with the only intention to use it in VMStateDescription structure. This approach is not very pretty. Adding extra flags to VMStateFlags enum for every other possible operation with size field seems redundant, and still it would't cover cases when we need to perform a set of operations to get size value. With this new .calc_size field we can calculate size of dynamic array in whichever way we need. Signed-off-by: Mitsyanko Igor <i.mitsyanko@samsung.com> --- hw/hw.h | 14 +++++++------- savevm.c | 14 ++++++++------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/hw/hw.h b/hw/hw.h index efa04d1..8ce4475 100644 --- a/hw/hw.h +++ b/hw/hw.h @@ -303,9 +303,9 @@ enum VMStateFlags { VMS_ARRAY_OF_POINTER = 0x040, VMS_VARRAY_UINT16 = 0x080, /* Array with size in uint16_t field */ VMS_VBUFFER = 0x100, /* Buffer with size in int32_t field */ - VMS_MULTIPLY = 0x200, /* multiply "size" field by field_size */ - VMS_VARRAY_UINT8 = 0x400, /* Array with size in uint8_t field*/ - VMS_VARRAY_UINT32 = 0x800, /* Array with size in uint32_t field*/ + VMS_CALC_SIZE = 0x200, /* calculate size of dynamic buffer */ + VMS_VARRAY_UINT8 = 0x400, /* Array with size in uint8_t field */ + VMS_VARRAY_UINT32 = 0x800, /* Array with size in uint32_t field */ }; typedef struct { @@ -321,6 +321,7 @@ typedef struct { const VMStateDescription *vmsd; int version_id; bool (*field_exists)(void *opaque, int version_id); + int (*calc_size)(void *opaque, int version_id); } VMStateField; typedef struct VMStateSubsection { @@ -584,14 +585,13 @@ extern const VMStateInfo vmstate_info_unused_buffer; .offset = vmstate_offset_buffer(_state, _field) + _start, \ } -#define VMSTATE_BUFFER_MULTIPLY(_field, _state, _version, _test, _start, _field_size, _multiply) { \ +#define VMSTATE_VBUFFER_CALCSIZE(_field, _state, _version, _test, _start, _calc_size) { \ .name = (stringify(_field)), \ .version_id = (_version), \ .field_exists = (_test), \ - .size_offset = vmstate_offset_value(_state, _field_size, uint32_t),\ - .size = (_multiply), \ + .calc_size = (_calc_size), \ .info = &vmstate_info_buffer, \ - .flags = VMS_VBUFFER|VMS_MULTIPLY, \ + .flags = VMS_VBUFFER|VMS_CALC_SIZE|VMS_POINTER, \ .offset = offsetof(_state, _field), \ .start = (_start), \ } diff --git a/savevm.c b/savevm.c index f153c25..19f8985 100644 --- a/savevm.c +++ b/savevm.c @@ -1412,9 +1412,10 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd, int size = field->size; if (field->flags & VMS_VBUFFER) { - size = *(int32_t *)(opaque+field->size_offset); - if (field->flags & VMS_MULTIPLY) { - size *= field->size; + if (field->flags & VMS_CALC_SIZE) { + size = field->calc_size(opaque, vmsd->version_id); + } else { + size = *(int32_t *)(opaque+field->size_offset); } } if (field->flags & VMS_ARRAY) { @@ -1476,9 +1477,10 @@ void vmstate_save_state(QEMUFile *f, const VMStateDescription *vmsd, int size = field->size; if (field->flags & VMS_VBUFFER) { - size = *(int32_t *)(opaque+field->size_offset); - if (field->flags & VMS_MULTIPLY) { - size *= field->size; + if (field->flags & VMS_CALC_SIZE) { + size = field->calc_size(opaque, vmsd->version_id); + } else { + size = *(int32_t *)(opaque+field->size_offset); } } if (field->flags & VMS_ARRAY) { -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 31+ messages in thread
* Re: [Qemu-devel] [PATCH 1/3] vmstate: introduce calc_size VMStateField 2011-12-26 10:03 ` [Qemu-devel] [PATCH 1/3] vmstate: introduce calc_size VMStateField Mitsyanko Igor @ 2011-12-26 15:20 ` Peter Maydell 2011-12-27 8:11 ` Mitsyanko Igor 2011-12-27 7:54 ` [Qemu-devel] [PATCH V2 1/3] vmstate: introduce get_bufsize entry in VMStateField Mitsyanko Igor 1 sibling, 1 reply; 31+ messages in thread From: Peter Maydell @ 2011-12-26 15:20 UTC (permalink / raw) To: Mitsyanko Igor Cc: e.voevodin, Juan Quintela, qemu-devel, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee On 26 December 2011 10:03, Mitsyanko Igor <i.mitsyanko@samsung.com> wrote: > New calc_size field in VMStateField is supposed to help us easily add save/restore > support of dynamically allocated buffers in device's states. > There are some cases when information on size of dynamically allocated buffer is > already presented in specific device's state structure, but in such a form, that > can not be used with existing VMStateField interface. Currently, we either can get size from > another variable in device's state as it is with VMSTATE_VBUFFER_* macros, or we can > also multiply value kept in a variable by a constant with VMSTATE_BUFFER_MULTIPLY > macro. If we need to perform any other action, we're forced to add additional > variable with size information to device state structure with the only intention > to use it in VMStateDescription structure. This approach is not very pretty. Adding extra > flags to VMStateFlags enum for every other possible operation with size field > seems redundant, and still it would't cover cases when we need to perform a set of > operations to get size value. > With this new .calc_size field we can calculate size of dynamic array in whichever > way we need. > > Signed-off-by: Mitsyanko Igor <i.mitsyanko@samsung.com> It seems a bit curious that this patch removes the existing (although admittedly unused) VMSTATE_BUFFER_MULTIPLY but doesn't actually say so in the commit message. > --- > hw/hw.h | 14 +++++++------- > savevm.c | 14 ++++++++------ > 2 files changed, 15 insertions(+), 13 deletions(-) > > diff --git a/hw/hw.h b/hw/hw.h > index efa04d1..8ce4475 100644 > --- a/hw/hw.h > +++ b/hw/hw.h > @@ -303,9 +303,9 @@ enum VMStateFlags { > VMS_ARRAY_OF_POINTER = 0x040, > VMS_VARRAY_UINT16 = 0x080, /* Array with size in uint16_t field */ > VMS_VBUFFER = 0x100, /* Buffer with size in int32_t field */ > - VMS_MULTIPLY = 0x200, /* multiply "size" field by field_size */ > - VMS_VARRAY_UINT8 = 0x400, /* Array with size in uint8_t field*/ > - VMS_VARRAY_UINT32 = 0x800, /* Array with size in uint32_t field*/ > + VMS_CALC_SIZE = 0x200, /* calculate size of dynamic buffer */ > + VMS_VARRAY_UINT8 = 0x400, /* Array with size in uint8_t field */ > + VMS_VARRAY_UINT32 = 0x800, /* Array with size in uint32_t field */ These unrelated whitespace fixes are confusing -- please drop them. -- PMM ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [Qemu-devel] [PATCH 1/3] vmstate: introduce calc_size VMStateField 2011-12-26 15:20 ` Peter Maydell @ 2011-12-27 8:11 ` Mitsyanko Igor 2011-12-27 13:10 ` Andreas Färber 0 siblings, 1 reply; 31+ messages in thread From: Mitsyanko Igor @ 2011-12-27 8:11 UTC (permalink / raw) To: qemu-devel; +Cc: Peter Maydell, Juan Quintela On 12/26/2011 07:20 PM, Peter Maydell wrote: > On 26 December 2011 10:03, Mitsyanko Igor<i.mitsyanko@samsung.com> wrote: >> New calc_size field in VMStateField is supposed to help us easily add save/restore >> support of dynamically allocated buffers in device's states. >> There are some cases when information on size of dynamically allocated buffer is >> already presented in specific device's state structure, but in such a form, that >> can not be used with existing VMStateField interface. Currently, we either can get size from >> another variable in device's state as it is with VMSTATE_VBUFFER_* macros, or we can >> also multiply value kept in a variable by a constant with VMSTATE_BUFFER_MULTIPLY >> macro. If we need to perform any other action, we're forced to add additional >> variable with size information to device state structure with the only intention >> to use it in VMStateDescription structure. This approach is not very pretty. Adding extra >> flags to VMStateFlags enum for every other possible operation with size field >> seems redundant, and still it would't cover cases when we need to perform a set of >> operations to get size value. >> With this new .calc_size field we can calculate size of dynamic array in whichever >> way we need. >> >> Signed-off-by: Mitsyanko Igor<i.mitsyanko@samsung.com> > > It seems a bit curious that this patch removes the existing (although admittedly > unused) VMSTATE_BUFFER_MULTIPLY but doesn't actually say so in the > commit message. > >> --- >> hw/hw.h | 14 +++++++------- >> savevm.c | 14 ++++++++------ >> 2 files changed, 15 insertions(+), 13 deletions(-) >> >> diff --git a/hw/hw.h b/hw/hw.h >> index efa04d1..8ce4475 100644 >> --- a/hw/hw.h >> +++ b/hw/hw.h >> @@ -303,9 +303,9 @@ enum VMStateFlags { >> VMS_ARRAY_OF_POINTER = 0x040, >> VMS_VARRAY_UINT16 = 0x080, /* Array with size in uint16_t field */ >> VMS_VBUFFER = 0x100, /* Buffer with size in int32_t field */ >> - VMS_MULTIPLY = 0x200, /* multiply "size" field by field_size */ >> - VMS_VARRAY_UINT8 = 0x400, /* Array with size in uint8_t field*/ >> - VMS_VARRAY_UINT32 = 0x800, /* Array with size in uint32_t field*/ >> + VMS_CALC_SIZE = 0x200, /* calculate size of dynamic buffer */ >> + VMS_VARRAY_UINT8 = 0x400, /* Array with size in uint8_t field */ >> + VMS_VARRAY_UINT32 = 0x800, /* Array with size in uint32_t field */ > > These unrelated whitespace fixes are confusing -- please drop them. QEMU wiki here http://wiki.qemu.org/Contribute/SubmitAPatch states that it's ok :) > It's OK to fix coding style issues in the immediate area (few lines) > of the lines you're changing.) Given some thoughts to it, I didn't like that this patch increases memory consumed by VMStateField. Maybe we should drop existing .size_offset field and replace it with generic get_buffsize callback, like we did in second version of this patch. It seems reasonable to me, even though existing .size_offset usage is pretty neat. -- Mitsyanko Igor ASWG, Moscow R&D center, Samsung Electronics email: i.mitsyanko@samsung.com ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [Qemu-devel] [PATCH 1/3] vmstate: introduce calc_size VMStateField 2011-12-27 8:11 ` Mitsyanko Igor @ 2011-12-27 13:10 ` Andreas Färber 2011-12-28 7:41 ` Mitsyanko Igor 0 siblings, 1 reply; 31+ messages in thread From: Andreas Färber @ 2011-12-27 13:10 UTC (permalink / raw) To: i.mitsyanko; +Cc: Peter Maydell, qemu-devel, Juan Quintela Am 27.12.2011 09:11, schrieb Mitsyanko Igor: > On 12/26/2011 07:20 PM, Peter Maydell wrote: >> On 26 December 2011 10:03, Mitsyanko Igor<i.mitsyanko@samsung.com> >> wrote: >>> diff --git a/hw/hw.h b/hw/hw.h >>> index efa04d1..8ce4475 100644 >>> --- a/hw/hw.h >>> +++ b/hw/hw.h >>> @@ -303,9 +303,9 @@ enum VMStateFlags { >>> VMS_ARRAY_OF_POINTER = 0x040, >>> VMS_VARRAY_UINT16 = 0x080, /* Array with size in uint16_t >>> field */ >>> VMS_VBUFFER = 0x100, /* Buffer with size in int32_t >>> field */ >>> - VMS_MULTIPLY = 0x200, /* multiply "size" field by >>> field_size */ >>> - VMS_VARRAY_UINT8 = 0x400, /* Array with size in uint8_t >>> field*/ >>> - VMS_VARRAY_UINT32 = 0x800, /* Array with size in uint32_t >>> field*/ >>> + VMS_CALC_SIZE = 0x200, /* calculate size of dynamic >>> buffer */ >>> + VMS_VARRAY_UINT8 = 0x400, /* Array with size in uint8_t >>> field */ >>> + VMS_VARRAY_UINT32 = 0x800, /* Array with size in uint32_t >>> field */ >> >> These unrelated whitespace fixes are confusing -- please drop them. > QEMU wiki here http://wiki.qemu.org/Contribute/SubmitAPatch states that > it's ok :) >> It's OK to fix coding style issues in the immediate area (few lines) > > of the lines you're changing.) It's not really a Coding Style issue though (just an aesthetic one), what's meant by the quote is to fix braces around the lines you touch. I'd suggest to put it in a separate preceding patch, then it gets fixed and we can still better see what you're changing here. (I'm in need of a mechanism like this for AHCI so please cc Juan and me on v2 of this patch.) Andreas -- SUSE LINUX Products GmbH, Maxfeldstr. 5, 90409 Nürnberg, Germany GF: Jeff Hawn, Jennifer Guild, Felix Imendörffer; HRB 16746 AG Nürnberg ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [Qemu-devel] [PATCH 1/3] vmstate: introduce calc_size VMStateField 2011-12-27 13:10 ` Andreas Färber @ 2011-12-28 7:41 ` Mitsyanko Igor 0 siblings, 0 replies; 31+ messages in thread From: Mitsyanko Igor @ 2011-12-28 7:41 UTC (permalink / raw) To: qemu-devel Cc: Peter Maydell, e.voevodin, Juan Quintela, kyungmin.park, d.solodkiy, Paolo Bonzini, m.kozlov, Andreas Färber On 12/27/2011 05:10 PM, Andreas Färber wrote: > Am 27.12.2011 09:11, schrieb Mitsyanko Igor: >> On 12/26/2011 07:20 PM, Peter Maydell wrote: >>> On 26 December 2011 10:03, Mitsyanko Igor<i.mitsyanko@samsung.com> >>> wrote: >>>> diff --git a/hw/hw.h b/hw/hw.h >>>> index efa04d1..8ce4475 100644 >>>> --- a/hw/hw.h >>>> +++ b/hw/hw.h >>>> @@ -303,9 +303,9 @@ enum VMStateFlags { >>>> VMS_ARRAY_OF_POINTER = 0x040, >>>> VMS_VARRAY_UINT16 = 0x080, /* Array with size in uint16_t >>>> field */ >>>> VMS_VBUFFER = 0x100, /* Buffer with size in int32_t >>>> field */ >>>> - VMS_MULTIPLY = 0x200, /* multiply "size" field by >>>> field_size */ >>>> - VMS_VARRAY_UINT8 = 0x400, /* Array with size in uint8_t >>>> field*/ >>>> - VMS_VARRAY_UINT32 = 0x800, /* Array with size in uint32_t >>>> field*/ >>>> + VMS_CALC_SIZE = 0x200, /* calculate size of dynamic >>>> buffer */ >>>> + VMS_VARRAY_UINT8 = 0x400, /* Array with size in uint8_t >>>> field */ >>>> + VMS_VARRAY_UINT32 = 0x800, /* Array with size in uint32_t >>>> field */ >>> >>> These unrelated whitespace fixes are confusing -- please drop them. >> QEMU wiki here http://wiki.qemu.org/Contribute/SubmitAPatch states that >> it's ok :) >>> It's OK to fix coding style issues in the immediate area (few lines)> >> of the lines you're changing.) > > It's not really a Coding Style issue though (just an aesthetic one), > what's meant by the quote is to fix braces around the lines you touch. > > I'd suggest to put it in a separate preceding patch, then it gets fixed > and we can still better see what you're changing here. If v2 of this patch will be accepted and we will drop VMS_MULTIPLY, I could add required spaces together with changing values given to VMS_VARRAY_UINT8 and VMS_VARRAY_UINT32 flags. Or this can be done by Paolo Bonzini in his "split hw/hw.h" patch, I cc him just in case he wouldn't mind to do this. > (I'm in need of a mechanism like this for AHCI so please cc Juan and me > on v2 of this patch.) > > Andreas > I'm also considering to change local size variable in vmstate_load_state() and vmstate_save_state() to size_t, it makes no sense to convert size_t field->size to intermediate int type just to pass it again as size_t to get() function. -- Mitsyanko Igor ASWG, Moscow R&D center, Samsung Electronics email: i.mitsyanko@samsung.com ^ permalink raw reply [flat|nested] 31+ messages in thread
* [Qemu-devel] [PATCH V2 1/3] vmstate: introduce get_bufsize entry in VMStateField 2011-12-26 10:03 ` [Qemu-devel] [PATCH 1/3] vmstate: introduce calc_size VMStateField Mitsyanko Igor 2011-12-26 15:20 ` Peter Maydell @ 2011-12-27 7:54 ` Mitsyanko Igor 1 sibling, 0 replies; 31+ messages in thread From: Mitsyanko Igor @ 2011-12-27 7:54 UTC (permalink / raw) To: qemu-devel Cc: Mitsyanko Igor, e.voevodin, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee New get_bufsize field in VMStateField is supposed to help us easily add save/restore support of dynamically allocated buffers in device's states. There are some cases when information about size of dynamically allocated buffer is already presented in specific device's state structure, but in such a form that can not be used with existing VMStateField interface. Currently, we either can get size from another variable in device's state as it is with VMSTATE_VBUFFER_* macros, or we can also multiply value kept in a variable by a constant with VMSTATE_BUFFER_MULTIPLY macro. If we need to perform any other action, we're forced to add additional variable with size information to device state structure with the only intention to use it in VMStateDescription. This approach is not very pretty. Adding extra flags to VMStateFlags enum for every other possible operation with size field seems redundant, and still it would't cover cases when we need to perform a set of operations to get size value. With get_bufsize callback we can calculate size of dynamic array in whichever way we need. We don't need .size_offset field anymore, so we can remove it from VMState Field structure to compensate for extra memory consuption because of get_bufsize addition. Macros VMSTATE_VBUFFER* are modified to use new callback instead of .size_offset. Macro VMSTATE_BUFFER_MULTIPLY and VMFlag VMS_MULTIPLY are removed completely as they are now redundant. Signed-off-by: Mitsyanko Igor <i.mitsyanko@samsung.com> --- hw/g364fb.c | 7 ++++++- hw/hw.h | 41 +++++++---------------------------------- hw/m48t59.c | 7 ++++++- hw/mac_nvram.c | 8 +++++++- hw/onenand.c | 7 ++++++- savevm.c | 10 ++-------- 6 files changed, 34 insertions(+), 46 deletions(-) diff --git a/hw/g364fb.c b/hw/g364fb.c index 34fb08c..1ab36c2 100644 --- a/hw/g364fb.c +++ b/hw/g364fb.c @@ -495,6 +495,11 @@ static int g364fb_post_load(void *opaque, int version_id) return 0; } +static int g364fb_get_vramsize(void *opaque, int version_id) +{ + return ((G364State *)opaque)->vram_size; +} + static const VMStateDescription vmstate_g364fb = { .name = "g364fb", .version_id = 1, @@ -502,7 +507,7 @@ static const VMStateDescription vmstate_g364fb = { .minimum_version_id_old = 1, .post_load = g364fb_post_load, .fields = (VMStateField[]) { - VMSTATE_VBUFFER_UINT32(vram, G364State, 1, NULL, 0, vram_size), + VMSTATE_VBUFFER(vram, G364State, 1, NULL, 0, g364fb_get_vramsize), VMSTATE_BUFFER_UNSAFE(color_palette, G364State, 0, 256 * 3), VMSTATE_BUFFER_UNSAFE(cursor_palette, G364State, 0, 9), VMSTATE_UINT16_ARRAY(cursor, G364State, 512), diff --git a/hw/hw.h b/hw/hw.h index efa04d1..a2a43b6 100644 --- a/hw/hw.h +++ b/hw/hw.h @@ -303,7 +303,6 @@ enum VMStateFlags { VMS_ARRAY_OF_POINTER = 0x040, VMS_VARRAY_UINT16 = 0x080, /* Array with size in uint16_t field */ VMS_VBUFFER = 0x100, /* Buffer with size in int32_t field */ - VMS_MULTIPLY = 0x200, /* multiply "size" field by field_size */ VMS_VARRAY_UINT8 = 0x400, /* Array with size in uint8_t field*/ VMS_VARRAY_UINT32 = 0x800, /* Array with size in uint32_t field*/ }; @@ -315,12 +314,12 @@ typedef struct { size_t start; int num; size_t num_offset; - size_t size_offset; const VMStateInfo *info; enum VMStateFlags flags; const VMStateDescription *vmsd; int version_id; bool (*field_exists)(void *opaque, int version_id); + int (*get_bufsize)(void *opaque, int version_id); } VMStateField; typedef struct VMStateSubsection { @@ -584,34 +583,11 @@ extern const VMStateInfo vmstate_info_unused_buffer; .offset = vmstate_offset_buffer(_state, _field) + _start, \ } -#define VMSTATE_BUFFER_MULTIPLY(_field, _state, _version, _test, _start, _field_size, _multiply) { \ +#define VMSTATE_VBUFFER(_field, _state, _version, _test, _start, _get_bufsize) { \ .name = (stringify(_field)), \ .version_id = (_version), \ .field_exists = (_test), \ - .size_offset = vmstate_offset_value(_state, _field_size, uint32_t),\ - .size = (_multiply), \ - .info = &vmstate_info_buffer, \ - .flags = VMS_VBUFFER|VMS_MULTIPLY, \ - .offset = offsetof(_state, _field), \ - .start = (_start), \ -} - -#define VMSTATE_VBUFFER(_field, _state, _version, _test, _start, _field_size) { \ - .name = (stringify(_field)), \ - .version_id = (_version), \ - .field_exists = (_test), \ - .size_offset = vmstate_offset_value(_state, _field_size, int32_t),\ - .info = &vmstate_info_buffer, \ - .flags = VMS_VBUFFER|VMS_POINTER, \ - .offset = offsetof(_state, _field), \ - .start = (_start), \ -} - -#define VMSTATE_VBUFFER_UINT32(_field, _state, _version, _test, _start, _field_size) { \ - .name = (stringify(_field)), \ - .version_id = (_version), \ - .field_exists = (_test), \ - .size_offset = vmstate_offset_value(_state, _field_size, uint32_t),\ + .get_bufsize = (_get_bufsize), \ .info = &vmstate_info_buffer, \ .flags = VMS_VBUFFER|VMS_POINTER, \ .offset = offsetof(_state, _field), \ @@ -891,14 +867,11 @@ extern const VMStateDescription vmstate_hid_ptr_device; #define VMSTATE_BUFFER_START_MIDDLE(_f, _s, _start) \ VMSTATE_STATIC_BUFFER(_f, _s, 0, NULL, _start, sizeof(typeof_field(_s, _f))) -#define VMSTATE_PARTIAL_VBUFFER(_f, _s, _size) \ - VMSTATE_VBUFFER(_f, _s, 0, NULL, 0, _size) - -#define VMSTATE_PARTIAL_VBUFFER_UINT32(_f, _s, _size) \ - VMSTATE_VBUFFER_UINT32(_f, _s, 0, NULL, 0, _size) +#define VMSTATE_PARTIAL_VBUFFER(_f, _s, _get_bufsize) \ + VMSTATE_VBUFFER(_f, _s, 0, NULL, 0, _get_bufsize) -#define VMSTATE_SUB_VBUFFER(_f, _s, _start, _size) \ - VMSTATE_VBUFFER(_f, _s, 0, NULL, _start, _size) +#define VMSTATE_SUB_VBUFFER(_f, _s, _start, _get_bufsize) \ + VMSTATE_VBUFFER(_f, _s, 0, NULL, _start, _get_bufsize) #define VMSTATE_BUFFER_TEST(_f, _s, _test) \ VMSTATE_STATIC_BUFFER(_f, _s, 0, _test, 0, sizeof(typeof_field(_s, _f))) diff --git a/hw/m48t59.c b/hw/m48t59.c index c043996..4e4c9f3 100644 --- a/hw/m48t59.c +++ b/hw/m48t59.c @@ -582,6 +582,11 @@ static const MemoryRegionOps nvram_ops = { .endianness = DEVICE_NATIVE_ENDIAN, }; +static int m48t59_get_bufsize(void *opaque, int version_id) +{ + return ((M48t59State *)opaque)->size; +} + static const VMStateDescription vmstate_m48t59 = { .name = "m48t59", .version_id = 1, @@ -590,7 +595,7 @@ static const VMStateDescription vmstate_m48t59 = { .fields = (VMStateField[]) { VMSTATE_UINT8(lock, M48t59State), VMSTATE_UINT16(addr, M48t59State), - VMSTATE_VBUFFER_UINT32(buffer, M48t59State, 0, NULL, 0, size), + VMSTATE_VBUFFER(buffer, M48t59State, 0, NULL, 0, m48t59_get_bufsize), VMSTATE_END_OF_LIST() } }; diff --git a/hw/mac_nvram.c b/hw/mac_nvram.c index ed0a2b7..f4367f8 100644 --- a/hw/mac_nvram.c +++ b/hw/mac_nvram.c @@ -100,13 +100,19 @@ static const MemoryRegionOps macio_nvram_ops = { .endianness = DEVICE_NATIVE_ENDIAN, }; +static int macio_nvram_get_datasize(void *opaque, int version_id) +{ + return ((MacIONVRAMState *)opaque)->size; +} + static const VMStateDescription vmstate_macio_nvram = { .name = "macio_nvram", .version_id = 1, .minimum_version_id = 1, .minimum_version_id_old = 1, .fields = (VMStateField[]) { - VMSTATE_VBUFFER_UINT32(data, MacIONVRAMState, 0, NULL, 0, size), + VMSTATE_VBUFFER(data, MacIONVRAMState, 0, NULL, 0, + macio_nvram_get_datasize), VMSTATE_END_OF_LIST() } }; diff --git a/hw/onenand.c b/hw/onenand.c index a9d8d67..3e2016b 100644 --- a/hw/onenand.c +++ b/hw/onenand.c @@ -160,6 +160,11 @@ static int onenand_post_load(void *opaque, int version_id) return 0; } +static int onenand_get_blockwpsize(void *opaque, int version_id) +{ + return ((OneNANDState *)opaque)->blocks; +} + static const VMStateDescription vmstate_onenand = { .name = "onenand", .version_id = 1, @@ -181,7 +186,7 @@ static const VMStateDescription vmstate_onenand = { VMSTATE_UINT16(intstatus, OneNANDState), VMSTATE_UINT16(wpstatus, OneNANDState), VMSTATE_INT32(secs_cur, OneNANDState), - VMSTATE_PARTIAL_VBUFFER(blockwp, OneNANDState, blocks), + VMSTATE_PARTIAL_VBUFFER(blockwp, OneNANDState, onenand_get_blockwpsize), VMSTATE_UINT8(ecc.cp, OneNANDState), VMSTATE_UINT16_ARRAY(ecc.lp, OneNANDState, 2), VMSTATE_UINT16(ecc.count, OneNANDState), diff --git a/savevm.c b/savevm.c index f153c25..831c50a 100644 --- a/savevm.c +++ b/savevm.c @@ -1412,10 +1412,7 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd, int size = field->size; if (field->flags & VMS_VBUFFER) { - size = *(int32_t *)(opaque+field->size_offset); - if (field->flags & VMS_MULTIPLY) { - size *= field->size; - } + size = field->get_bufsize(opaque, version_id); } if (field->flags & VMS_ARRAY) { n_elems = field->num; @@ -1476,10 +1473,7 @@ void vmstate_save_state(QEMUFile *f, const VMStateDescription *vmsd, int size = field->size; if (field->flags & VMS_VBUFFER) { - size = *(int32_t *)(opaque+field->size_offset); - if (field->flags & VMS_MULTIPLY) { - size *= field->size; - } + size = field->get_bufsize(opaque, vmsd->version_id); } if (field->flags & VMS_ARRAY) { n_elems = field->num; -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 31+ messages in thread
* [Qemu-devel] [PATCH 2/3] hw/sd.c: add SD card save/load support 2011-12-26 10:03 [Qemu-devel] [PATCH 0/3] Improve SD controllers emulation Mitsyanko Igor 2011-12-26 10:03 ` [Qemu-devel] [PATCH 1/3] vmstate: introduce calc_size VMStateField Mitsyanko Igor @ 2011-12-26 10:03 ` Mitsyanko Igor 2011-12-26 14:58 ` Peter Maydell 2011-12-26 10:03 ` [Qemu-devel] [PATCH 3/3] hw/: Introduce spec. ver. 2.00 compliant SD host controller Mitsyanko Igor ` (2 subsequent siblings) 4 siblings, 1 reply; 31+ messages in thread From: Mitsyanko Igor @ 2011-12-26 10:03 UTC (permalink / raw) To: qemu-devel Cc: Mitsyanko Igor, e.voevodin, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee We couldn't properly implement save/restore functionality of SD host controllers states without SD card's state VMStateDescription implementation. This patch updates SD card emulation to support save/load of card's state. Signed-off-by: Mitsyanko Igor <i.mitsyanko@samsung.com> --- hw/milkymist-memcard.c | 2 + hw/sd.c | 115 +++++++++++++++++++++++++++++++++--------------- hw/sd.h | 1 + 3 files changed, 82 insertions(+), 36 deletions(-) diff --git a/hw/milkymist-memcard.c b/hw/milkymist-memcard.c index 865a46c..6e8b0e4 100644 --- a/hw/milkymist-memcard.c +++ b/hw/milkymist-memcard.c @@ -266,6 +266,8 @@ static const VMStateDescription vmstate_milkymist_memcard = { .minimum_version_id = 1, .minimum_version_id_old = 1, .fields = (VMStateField[]) { + VMSTATE_STRUCT_POINTER(card, MilkymistMemcardState, sd_vmstate, + SDState *), VMSTATE_INT32(command_write_ptr, MilkymistMemcardState), VMSTATE_INT32(response_read_ptr, MilkymistMemcardState), VMSTATE_INT32(response_len, MilkymistMemcardState), diff --git a/hw/sd.c b/hw/sd.c index 07eb263..2b489d3 100644 --- a/hw/sd.c +++ b/hw/sd.c @@ -54,24 +54,28 @@ typedef enum { sd_illegal = -2, } sd_rsp_type_t; +enum { + sd_inactive, + sd_card_identification_mode, + sd_data_transfer_mode, +}; + +enum { + sd_inactive_state = -1, + sd_idle_state = 0, + sd_ready_state, + sd_identification_state, + sd_standby_state, + sd_transfer_state, + sd_sendingdata_state, + sd_receivingdata_state, + sd_programming_state, + sd_disconnect_state, +}; + struct SDState { - enum { - sd_inactive, - sd_card_identification_mode, - sd_data_transfer_mode, - } mode; - enum { - sd_inactive_state = -1, - sd_idle_state = 0, - sd_ready_state, - sd_identification_state, - sd_standby_state, - sd_transfer_state, - sd_sendingdata_state, - sd_receivingdata_state, - sd_programming_state, - sd_disconnect_state, - } state; + uint32_t mode; + int32_t state; uint32_t ocr; uint8_t scr[8]; uint8_t cid[16]; @@ -81,22 +85,22 @@ struct SDState { uint8_t sd_status[64]; uint32_t vhs; int wp_switch; - int *wp_groups; + uint8_t *wp_groups; uint64_t size; - int blk_len; + uint32_t blk_len; uint32_t erase_start; uint32_t erase_end; uint8_t pwd[16]; - int pwd_len; - int function_group[6]; + uint32_t pwd_len; + uint8_t function_group[6]; - int spi; - int current_cmd; + uint8_t spi; + uint8_t current_cmd; /* True if we will handle the next command as an ACMD. Note that this does * *not* track the APP_CMD status bit! */ - int expecting_acmd; - int blk_written; + bool expecting_acmd; + uint32_t blk_written; uint64_t data_start; uint32_t data_offset; uint8_t data[512]; @@ -105,7 +109,7 @@ struct SDState { BlockDriverState *bdrv; uint8_t *buf; - int enable; + bool enable; }; static void sd_set_mode(SDState *sd) @@ -415,14 +419,14 @@ static void sd_reset(SDState *sd, BlockDriverState *bdrv) if (sd->wp_groups) g_free(sd->wp_groups); sd->wp_switch = bdrv ? bdrv_is_read_only(bdrv) : 0; - sd->wp_groups = (int *) g_malloc0(sizeof(int) * sect); - memset(sd->function_group, 0, sizeof(int) * 6); + sd->wp_groups = (uint8_t *)g_malloc0(sect); + memset(sd->function_group, 0, 6); sd->erase_start = 0; sd->erase_end = 0; sd->size = size; sd->blk_len = 0x200; sd->pwd_len = 0; - sd->expecting_acmd = 0; + sd->expecting_acmd = false; } static void sd_cardchange(void *opaque, bool load) @@ -451,7 +455,7 @@ SDState *sd_init(BlockDriverState *bs, int is_spi) sd = (SDState *) g_malloc0(sizeof(SDState)); sd->buf = qemu_blockalign(bs, 512); sd->spi = is_spi; - sd->enable = 1; + sd->enable = true; sd_reset(sd, bs); if (sd->bdrv) { bdrv_attach_dev_nofail(sd->bdrv, sd); @@ -534,7 +538,7 @@ static void sd_function_switch(SDState *sd, uint32_t arg) sd->data[66] = crc & 0xff; } -static inline int sd_wp_addr(SDState *sd, uint32_t addr) +static inline uint8_t sd_wp_addr(SDState *sd, uint32_t addr) { return sd->wp_groups[addr >> (HWBLOCK_SHIFT + SECTOR_SHIFT + WPGROUP_SHIFT)]; @@ -560,7 +564,7 @@ static void sd_lock_command(SDState *sd) sd->card_status |= LOCK_UNLOCK_FAILED; return; } - memset(sd->wp_groups, 0, sizeof(int) * (sd->size >> + memset(sd->wp_groups, 0, (sd->size >> (HWBLOCK_SHIFT + SECTOR_SHIFT + WPGROUP_SHIFT))); sd->csd[14] &= ~0x10; sd->card_status &= ~CARD_IS_LOCKED; @@ -1125,7 +1129,7 @@ static sd_rsp_type_t sd_normal_command(SDState *sd, if (sd->rca != rca) return sd_r0; - sd->expecting_acmd = 1; + sd->expecting_acmd = true; sd->card_status |= APP_CMD; return sd_r1; @@ -1307,7 +1311,7 @@ int sd_do_command(SDState *sd, SDRequest *req, if (sd->card_status & CARD_IS_LOCKED) { if (!cmd_valid_while_locked(sd, req)) { sd->card_status |= ILLEGAL_COMMAND; - sd->expecting_acmd = 0; + sd->expecting_acmd = false; fprintf(stderr, "SD: Card is locked\n"); rtype = sd_illegal; goto send_response; @@ -1318,7 +1322,7 @@ int sd_do_command(SDState *sd, SDRequest *req, sd_set_mode(sd); if (sd->expecting_acmd) { - sd->expecting_acmd = 0; + sd->expecting_acmd = false; rtype = sd_app_command(sd, *req); } else { rtype = sd_normal_command(sd, *req); @@ -1706,5 +1710,44 @@ int sd_data_ready(SDState *sd) void sd_enable(SDState *sd, int enable) { - sd->enable = enable; + sd->enable = enable ? true : false; +} + +static int sd_vmstate_get_wpgroups_size(void *opaque, int version_id) +{ + SDState *sd = (SDState *)opaque; + return sd->size >> (HWBLOCK_SHIFT + SECTOR_SHIFT + WPGROUP_SHIFT); } + +const VMStateDescription sd_vmstate = { + .name = "sd_card", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(mode, SDState), + VMSTATE_INT32(state, SDState), + VMSTATE_UINT8_ARRAY(cid, SDState, 16), + VMSTATE_UINT8_ARRAY(csd, SDState, 16), + VMSTATE_UINT16(rca, SDState), + VMSTATE_UINT32(card_status, SDState), + VMSTATE_PARTIAL_BUFFER(sd_status, SDState, 1), + VMSTATE_UINT32(vhs, SDState), + VMSTATE_VBUFFER_CALCSIZE(wp_groups, SDState, + 1, NULL, 0, sd_vmstate_get_wpgroups_size), + VMSTATE_UINT32(blk_len, SDState), + VMSTATE_UINT32(erase_start, SDState), + VMSTATE_UINT32(erase_end, SDState), + VMSTATE_UINT8_ARRAY(pwd, SDState, 16), + VMSTATE_UINT32(pwd_len, SDState), + VMSTATE_UINT8_ARRAY(function_group, SDState, 6), + VMSTATE_UINT8(current_cmd, SDState), + VMSTATE_BOOL(expecting_acmd, SDState), + VMSTATE_UINT32(blk_written, SDState), + VMSTATE_UINT64(data_start, SDState), + VMSTATE_UINT32(data_offset, SDState), + VMSTATE_UINT8_ARRAY(data, SDState, 512), + VMSTATE_BUFFER_UNSAFE(buf, SDState, 1, 512), + VMSTATE_BOOL(enable, SDState), + VMSTATE_END_OF_LIST() + } +}; diff --git a/hw/sd.h b/hw/sd.h index ac4b7c4..a2489c6 100644 --- a/hw/sd.h +++ b/hw/sd.h @@ -66,6 +66,7 @@ typedef struct { } SDRequest; typedef struct SDState SDState; +extern const VMStateDescription sd_vmstate; SDState *sd_init(BlockDriverState *bs, int is_spi); int sd_do_command(SDState *sd, SDRequest *req, -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 31+ messages in thread
* Re: [Qemu-devel] [PATCH 2/3] hw/sd.c: add SD card save/load support 2011-12-26 10:03 ` [Qemu-devel] [PATCH 2/3] hw/sd.c: add SD card save/load support Mitsyanko Igor @ 2011-12-26 14:58 ` Peter Maydell 2011-12-27 11:27 ` Mitsyanko Igor ` (2 more replies) 0 siblings, 3 replies; 31+ messages in thread From: Peter Maydell @ 2011-12-26 14:58 UTC (permalink / raw) To: Mitsyanko Igor Cc: e.voevodin, Juan Quintela, qemu-devel, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee On 26 December 2011 10:03, Mitsyanko Igor <i.mitsyanko@samsung.com> wrote: > We couldn't properly implement save/restore functionality of SD host controllers > states without SD card's state VMStateDescription implementation. This patch > updates SD card emulation to support save/load of card's state. > > Signed-off-by: Mitsyanko Igor <i.mitsyanko@samsung.com> Ah, cool. I'd had a quick go at adding save/restore to sd.c but ran aground on the wp_groups array issue. (cc'd Juan as vmstate/migration expert) > --- > hw/milkymist-memcard.c | 2 + > hw/sd.c | 115 +++++++++++++++++++++++++++++++++--------------- > hw/sd.h | 1 + > 3 files changed, 82 insertions(+), 36 deletions(-) > > diff --git a/hw/milkymist-memcard.c b/hw/milkymist-memcard.c > index 865a46c..6e8b0e4 100644 > --- a/hw/milkymist-memcard.c > +++ b/hw/milkymist-memcard.c > @@ -266,6 +266,8 @@ static const VMStateDescription vmstate_milkymist_memcard = { > .minimum_version_id = 1, > .minimum_version_id_old = 1, > .fields = (VMStateField[]) { > + VMSTATE_STRUCT_POINTER(card, MilkymistMemcardState, sd_vmstate, > + SDState *), This doesn't seem like the right approach to me -- the migration state for the controller shouldn't have to include all the state for the card. Instead I think it would be better for sd.c to register a vmstate struct (by calling vmstate_register in sd_init) and thus handle its own saving/loading. Then if/when we make sd.c a proper qemu object we won't need to change the migration state for all the controllers. > VMSTATE_INT32(command_write_ptr, MilkymistMemcardState), > VMSTATE_INT32(response_read_ptr, MilkymistMemcardState), > VMSTATE_INT32(response_len, MilkymistMemcardState), > diff --git a/hw/sd.c b/hw/sd.c > index 07eb263..2b489d3 100644 > --- a/hw/sd.c > +++ b/hw/sd.c > @@ -54,24 +54,28 @@ typedef enum { > sd_illegal = -2, > } sd_rsp_type_t; > > +enum { > + sd_inactive, > + sd_card_identification_mode, > + sd_data_transfer_mode, > +}; > + > +enum { > + sd_inactive_state = -1, > + sd_idle_state = 0, > + sd_ready_state, > + sd_identification_state, > + sd_standby_state, > + sd_transfer_state, > + sd_sendingdata_state, > + sd_receivingdata_state, > + sd_programming_state, > + sd_disconnect_state, > +}; > + > struct SDState { > - enum { > - sd_inactive, > - sd_card_identification_mode, > - sd_data_transfer_mode, > - } mode; > - enum { > - sd_inactive_state = -1, > - sd_idle_state = 0, > - sd_ready_state, > - sd_identification_state, > - sd_standby_state, > - sd_transfer_state, > - sd_sendingdata_state, > - sd_receivingdata_state, > - sd_programming_state, > - sd_disconnect_state, > - } state; > + uint32_t mode; > + int32_t state; > uint32_t ocr; > uint8_t scr[8]; > uint8_t cid[16]; > @@ -81,22 +85,22 @@ struct SDState { > uint8_t sd_status[64]; > uint32_t vhs; > int wp_switch; > - int *wp_groups; > + uint8_t *wp_groups; > uint64_t size; > - int blk_len; > + uint32_t blk_len; > uint32_t erase_start; > uint32_t erase_end; > uint8_t pwd[16]; > - int pwd_len; > - int function_group[6]; > + uint32_t pwd_len; > + uint8_t function_group[6]; > > - int spi; > - int current_cmd; > + uint8_t spi; > + uint8_t current_cmd; > /* True if we will handle the next command as an ACMD. Note that this does > * *not* track the APP_CMD status bit! > */ > - int expecting_acmd; > - int blk_written; > + bool expecting_acmd; Why have you changed expecting_acmd and enable to bool, but spi to a uint8_t and wp_groups[] to a uint8_t[] ? This isn't very consistent. (I'm vaguely against bool because you then end up making other changes to change '1' to 'true' and so on.) > + uint32_t blk_written; > uint64_t data_start; > uint32_t data_offset; > uint8_t data[512]; > @@ -105,7 +109,7 @@ struct SDState { > BlockDriverState *bdrv; > uint8_t *buf; > > - int enable; > + bool enable; > }; > > static void sd_set_mode(SDState *sd) > @@ -415,14 +419,14 @@ static void sd_reset(SDState *sd, BlockDriverState *bdrv) > if (sd->wp_groups) > g_free(sd->wp_groups); > sd->wp_switch = bdrv ? bdrv_is_read_only(bdrv) : 0; > - sd->wp_groups = (int *) g_malloc0(sizeof(int) * sect); > - memset(sd->function_group, 0, sizeof(int) * 6); > + sd->wp_groups = (uint8_t *)g_malloc0(sect); > + memset(sd->function_group, 0, 6); Since you're touching this line anyway, can we have sizeof(sd->function_group) rather than that hardcoded 6 ? > sd->erase_start = 0; > sd->erase_end = 0; > sd->size = size; > sd->blk_len = 0x200; > sd->pwd_len = 0; > - sd->expecting_acmd = 0; > + sd->expecting_acmd = false; > } > > static void sd_cardchange(void *opaque, bool load) > @@ -451,7 +455,7 @@ SDState *sd_init(BlockDriverState *bs, int is_spi) > sd = (SDState *) g_malloc0(sizeof(SDState)); > sd->buf = qemu_blockalign(bs, 512); > sd->spi = is_spi; > - sd->enable = 1; > + sd->enable = true; > sd_reset(sd, bs); > if (sd->bdrv) { > bdrv_attach_dev_nofail(sd->bdrv, sd); > @@ -534,7 +538,7 @@ static void sd_function_switch(SDState *sd, uint32_t arg) > sd->data[66] = crc & 0xff; > } > > -static inline int sd_wp_addr(SDState *sd, uint32_t addr) > +static inline uint8_t sd_wp_addr(SDState *sd, uint32_t addr) > { > return sd->wp_groups[addr >> > (HWBLOCK_SHIFT + SECTOR_SHIFT + WPGROUP_SHIFT)]; > @@ -560,7 +564,7 @@ static void sd_lock_command(SDState *sd) > sd->card_status |= LOCK_UNLOCK_FAILED; > return; > } > - memset(sd->wp_groups, 0, sizeof(int) * (sd->size >> > + memset(sd->wp_groups, 0, (sd->size >> > (HWBLOCK_SHIFT + SECTOR_SHIFT + WPGROUP_SHIFT))); > sd->csd[14] &= ~0x10; > sd->card_status &= ~CARD_IS_LOCKED; > @@ -1125,7 +1129,7 @@ static sd_rsp_type_t sd_normal_command(SDState *sd, > if (sd->rca != rca) > return sd_r0; > > - sd->expecting_acmd = 1; > + sd->expecting_acmd = true; > sd->card_status |= APP_CMD; > return sd_r1; > > @@ -1307,7 +1311,7 @@ int sd_do_command(SDState *sd, SDRequest *req, > if (sd->card_status & CARD_IS_LOCKED) { > if (!cmd_valid_while_locked(sd, req)) { > sd->card_status |= ILLEGAL_COMMAND; > - sd->expecting_acmd = 0; > + sd->expecting_acmd = false; > fprintf(stderr, "SD: Card is locked\n"); > rtype = sd_illegal; > goto send_response; > @@ -1318,7 +1322,7 @@ int sd_do_command(SDState *sd, SDRequest *req, > sd_set_mode(sd); > > if (sd->expecting_acmd) { > - sd->expecting_acmd = 0; > + sd->expecting_acmd = false; > rtype = sd_app_command(sd, *req); > } else { > rtype = sd_normal_command(sd, *req); > @@ -1706,5 +1710,44 @@ int sd_data_ready(SDState *sd) > > void sd_enable(SDState *sd, int enable) > { > - sd->enable = enable; > + sd->enable = enable ? true : false; This kind of thing is why I don't like bool :-) > +} > + > +static int sd_vmstate_get_wpgroups_size(void *opaque, int version_id) > +{ > + SDState *sd = (SDState *)opaque; > + return sd->size >> (HWBLOCK_SHIFT + SECTOR_SHIFT + WPGROUP_SHIFT); > } > + > +const VMStateDescription sd_vmstate = { > + .name = "sd_card", > + .version_id = 1, > + .minimum_version_id = 1, > + .fields = (VMStateField[]) { > + VMSTATE_UINT32(mode, SDState), > + VMSTATE_INT32(state, SDState), > + VMSTATE_UINT8_ARRAY(cid, SDState, 16), > + VMSTATE_UINT8_ARRAY(csd, SDState, 16), > + VMSTATE_UINT16(rca, SDState), > + VMSTATE_UINT32(card_status, SDState), > + VMSTATE_PARTIAL_BUFFER(sd_status, SDState, 1), > + VMSTATE_UINT32(vhs, SDState), > + VMSTATE_VBUFFER_CALCSIZE(wp_groups, SDState, > + 1, NULL, 0, sd_vmstate_get_wpgroups_size), > + VMSTATE_UINT32(blk_len, SDState), > + VMSTATE_UINT32(erase_start, SDState), > + VMSTATE_UINT32(erase_end, SDState), > + VMSTATE_UINT8_ARRAY(pwd, SDState, 16), > + VMSTATE_UINT32(pwd_len, SDState), > + VMSTATE_UINT8_ARRAY(function_group, SDState, 6), > + VMSTATE_UINT8(current_cmd, SDState), > + VMSTATE_BOOL(expecting_acmd, SDState), > + VMSTATE_UINT32(blk_written, SDState), > + VMSTATE_UINT64(data_start, SDState), > + VMSTATE_UINT32(data_offset, SDState), > + VMSTATE_UINT8_ARRAY(data, SDState, 512), > + VMSTATE_BUFFER_UNSAFE(buf, SDState, 1, 512), > + VMSTATE_BOOL(enable, SDState), > + VMSTATE_END_OF_LIST() > + } > +}; > diff --git a/hw/sd.h b/hw/sd.h > index ac4b7c4..a2489c6 100644 > --- a/hw/sd.h > +++ b/hw/sd.h > @@ -66,6 +66,7 @@ typedef struct { > } SDRequest; > > typedef struct SDState SDState; > +extern const VMStateDescription sd_vmstate; > > SDState *sd_init(BlockDriverState *bs, int is_spi); > int sd_do_command(SDState *sd, SDRequest *req, > -- > 1.7.4.1 -- PMM ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [Qemu-devel] [PATCH 2/3] hw/sd.c: add SD card save/load support 2011-12-26 14:58 ` Peter Maydell @ 2011-12-27 11:27 ` Mitsyanko Igor 2011-12-27 13:27 ` Andreas Färber 2011-12-27 14:13 ` Avi Kivity 2 siblings, 0 replies; 31+ messages in thread From: Mitsyanko Igor @ 2011-12-27 11:27 UTC (permalink / raw) To: qemu-devel; +Cc: Juan Quintela On 12/26/2011 06:58 PM, Peter Maydell wrote: > On 26 December 2011 10:03, Mitsyanko Igor<i.mitsyanko@samsung.com> wrote: >> We couldn't properly implement save/restore functionality of SD host controllers >> states without SD card's state VMStateDescription implementation. This patch >> updates SD card emulation to support save/load of card's state. >> >> Signed-off-by: Mitsyanko Igor<i.mitsyanko@samsung.com> > > Ah, cool. I'd had a quick go at adding save/restore to sd.c but > ran aground on the wp_groups array issue. > > (cc'd Juan as vmstate/migration expert) > >> --- >> hw/milkymist-memcard.c | 2 + >> hw/sd.c | 115 +++++++++++++++++++++++++++++++++--------------- >> hw/sd.h | 1 + >> 3 files changed, 82 insertions(+), 36 deletions(-) >> >> diff --git a/hw/milkymist-memcard.c b/hw/milkymist-memcard.c >> index 865a46c..6e8b0e4 100644 >> --- a/hw/milkymist-memcard.c >> +++ b/hw/milkymist-memcard.c >> @@ -266,6 +266,8 @@ static const VMStateDescription vmstate_milkymist_memcard = { >> .minimum_version_id = 1, >> .minimum_version_id_old = 1, >> .fields = (VMStateField[]) { >> + VMSTATE_STRUCT_POINTER(card, MilkymistMemcardState, sd_vmstate, >> + SDState *), > > This doesn't seem like the right approach to me -- the migration state > for the controller shouldn't have to include all the state for the card. > Instead I think it would be better for sd.c to register a vmstate struct > (by calling vmstate_register in sd_init) and thus handle its own > saving/loading. Then if/when we make sd.c a proper qemu object we > won't need to change the migration state for all the controllers. > Good point, thanks. >> VMSTATE_INT32(command_write_ptr, MilkymistMemcardState), >> VMSTATE_INT32(response_read_ptr, MilkymistMemcardState), >> VMSTATE_INT32(response_len, MilkymistMemcardState), >> diff --git a/hw/sd.c b/hw/sd.c >> index 07eb263..2b489d3 100644 >> --- a/hw/sd.c >> +++ b/hw/sd.c >> @@ -54,24 +54,28 @@ typedef enum { >> sd_illegal = -2, >> } sd_rsp_type_t; >> >> +enum { >> + sd_inactive, >> + sd_card_identification_mode, >> + sd_data_transfer_mode, >> +}; >> + >> +enum { >> + sd_inactive_state = -1, >> + sd_idle_state = 0, >> + sd_ready_state, >> + sd_identification_state, >> + sd_standby_state, >> + sd_transfer_state, >> + sd_sendingdata_state, >> + sd_receivingdata_state, >> + sd_programming_state, >> + sd_disconnect_state, >> +}; >> + >> struct SDState { >> - enum { >> - sd_inactive, >> - sd_card_identification_mode, >> - sd_data_transfer_mode, >> - } mode; >> - enum { >> - sd_inactive_state = -1, >> - sd_idle_state = 0, >> - sd_ready_state, >> - sd_identification_state, >> - sd_standby_state, >> - sd_transfer_state, >> - sd_sendingdata_state, >> - sd_receivingdata_state, >> - sd_programming_state, >> - sd_disconnect_state, >> - } state; >> + uint32_t mode; >> + int32_t state; >> uint32_t ocr; >> uint8_t scr[8]; >> uint8_t cid[16]; >> @@ -81,22 +85,22 @@ struct SDState { >> uint8_t sd_status[64]; >> uint32_t vhs; >> int wp_switch; >> - int *wp_groups; >> + uint8_t *wp_groups; >> uint64_t size; >> - int blk_len; >> + uint32_t blk_len; >> uint32_t erase_start; >> uint32_t erase_end; >> uint8_t pwd[16]; >> - int pwd_len; >> - int function_group[6]; >> + uint32_t pwd_len; >> + uint8_t function_group[6]; >> >> - int spi; >> - int current_cmd; >> + uint8_t spi; >> + uint8_t current_cmd; >> /* True if we will handle the next command as an ACMD. Note that this does >> * *not* track the APP_CMD status bit! >> */ >> - int expecting_acmd; >> - int blk_written; >> + bool expecting_acmd; > > Why have you changed expecting_acmd and enable to bool, but spi > to a uint8_t and wp_groups[] to a uint8_t[] ? This isn't very > consistent. (I'm vaguely against bool because you then end up > making other changes to change '1' to 'true' and so on.) > I tried to preserve alignment and potentially avoid unnecessary memory consumption in arrays since bool size is implementation defined, otherwise I would have change everything to bool. I'll convert everything to uint8_t next time, you're right, this'll make things simpler. >> + uint32_t blk_written; >> uint64_t data_start; >> uint32_t data_offset; >> uint8_t data[512]; >> @@ -105,7 +109,7 @@ struct SDState { >> BlockDriverState *bdrv; >> uint8_t *buf; >> >> - int enable; >> + bool enable; >> }; >> >> static void sd_set_mode(SDState *sd) >> @@ -415,14 +419,14 @@ static void sd_reset(SDState *sd, BlockDriverState *bdrv) >> if (sd->wp_groups) >> g_free(sd->wp_groups); >> sd->wp_switch = bdrv ? bdrv_is_read_only(bdrv) : 0; >> - sd->wp_groups = (int *) g_malloc0(sizeof(int) * sect); >> - memset(sd->function_group, 0, sizeof(int) * 6); >> + sd->wp_groups = (uint8_t *)g_malloc0(sect); >> + memset(sd->function_group, 0, 6); > > Since you're touching this line anyway, can we have > sizeof(sd->function_group) rather than that hardcoded 6 ? > Sure. -- Mitsyanko Igor ASWG, Moscow R&D center, Samsung Electronics email: i.mitsyanko@samsung.com ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [Qemu-devel] [PATCH 2/3] hw/sd.c: add SD card save/load support 2011-12-26 14:58 ` Peter Maydell 2011-12-27 11:27 ` Mitsyanko Igor @ 2011-12-27 13:27 ` Andreas Färber 2011-12-27 13:54 ` Mitsyanko Igor 2011-12-27 14:13 ` Avi Kivity 2 siblings, 1 reply; 31+ messages in thread From: Andreas Färber @ 2011-12-27 13:27 UTC (permalink / raw) To: Peter Maydell Cc: Mitsyanko Igor, e.voevodin, Juan Quintela, qemu-devel, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee Am 26.12.2011 15:58, schrieb Peter Maydell: >> diff --git a/hw/sd.c b/hw/sd.c >> index 07eb263..2b489d3 100644 >> --- a/hw/sd.c >> +++ b/hw/sd.c >> @@ -81,22 +85,22 @@ struct SDState { >> uint8_t sd_status[64]; >> uint32_t vhs; >> int wp_switch; >> - int *wp_groups; >> + uint8_t *wp_groups; >> uint64_t size; >> - int blk_len; >> + uint32_t blk_len; >> uint32_t erase_start; >> uint32_t erase_end; >> uint8_t pwd[16]; >> - int pwd_len; >> - int function_group[6]; >> + uint32_t pwd_len; >> + uint8_t function_group[6]; >> >> - int spi; >> - int current_cmd; >> + uint8_t spi; >> + uint8_t current_cmd; >> /* True if we will handle the next command as an ACMD. Note that this does >> * *not* track the APP_CMD status bit! >> */ >> - int expecting_acmd; >> - int blk_written; >> + bool expecting_acmd; > > Why have you changed expecting_acmd and enable to bool, but spi > to a uint8_t and wp_groups[] to a uint8_t[] ? This isn't very > consistent. (I'm vaguely against bool because you then end up > making other changes to change '1' to 'true' and so on.) >> @@ -1706,5 +1710,44 @@ int sd_data_ready(SDState *sd) >> >> void sd_enable(SDState *sd, int enable) >> { >> - sd->enable = enable; >> + sd->enable = enable ? true : false; > > This kind of thing is why I don't like bool :-) x = 1 should work with bool when not doing x == true anywhere, here !!enable would be an alternative. Maybe change int enable to bool, too? Andreas -- SUSE LINUX Products GmbH, Maxfeldstr. 5, 90409 Nürnberg, Germany GF: Jeff Hawn, Jennifer Guild, Felix Imendörffer; HRB 16746 AG Nürnberg ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [Qemu-devel] [PATCH 2/3] hw/sd.c: add SD card save/load support 2011-12-27 13:27 ` Andreas Färber @ 2011-12-27 13:54 ` Mitsyanko Igor 0 siblings, 0 replies; 31+ messages in thread From: Mitsyanko Igor @ 2011-12-27 13:54 UTC (permalink / raw) To: qemu-devel Cc: Peter Maydell, e.voevodin, Juan Quintela, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee, Andreas Färber On 12/27/2011 05:27 PM, Andreas Färber wrote: > Am 26.12.2011 15:58, schrieb Peter Maydell: >>> diff --git a/hw/sd.c b/hw/sd.c >>> index 07eb263..2b489d3 100644 >>> --- a/hw/sd.c >>> +++ b/hw/sd.c > >>> @@ -81,22 +85,22 @@ struct SDState { >>> uint8_t sd_status[64]; >>> uint32_t vhs; >>> int wp_switch; >>> - int *wp_groups; >>> + uint8_t *wp_groups; >>> uint64_t size; >>> - int blk_len; >>> + uint32_t blk_len; >>> uint32_t erase_start; >>> uint32_t erase_end; >>> uint8_t pwd[16]; >>> - int pwd_len; >>> - int function_group[6]; >>> + uint32_t pwd_len; >>> + uint8_t function_group[6]; >>> >>> - int spi; >>> - int current_cmd; >>> + uint8_t spi; >>> + uint8_t current_cmd; >>> /* True if we will handle the next command as an ACMD. Note that this does >>> * *not* track the APP_CMD status bit! >>> */ >>> - int expecting_acmd; >>> - int blk_written; >>> + bool expecting_acmd; >> >> Why have you changed expecting_acmd and enable to bool, but spi >> to a uint8_t and wp_groups[] to a uint8_t[] ? This isn't very >> consistent. (I'm vaguely against bool because you then end up >> making other changes to change '1' to 'true' and so on.) > >>> @@ -1706,5 +1710,44 @@ int sd_data_ready(SDState *sd) >>> >>> void sd_enable(SDState *sd, int enable) >>> { >>> - sd->enable = enable; >>> + sd->enable = enable ? true : false; >> >> This kind of thing is why I don't like bool :-) > > x = 1 should work with bool when not doing x == true anywhere, here > !!enable would be an alternative. Maybe change int enable to bool, too? > > Andreas > As Peter mentioned previously, we should use either uint8_t or bool for all binary variables for consistency, and we probably shouldn't use bool for wp_groups array since sizeof(bool) uncertainty. So I'm considering just make everything uint8_t. -- Mitsyanko Igor ASWG, Moscow R&D center, Samsung Electronics email: i.mitsyanko@samsung.com ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [Qemu-devel] [PATCH 2/3] hw/sd.c: add SD card save/load support 2011-12-26 14:58 ` Peter Maydell 2011-12-27 11:27 ` Mitsyanko Igor 2011-12-27 13:27 ` Andreas Färber @ 2011-12-27 14:13 ` Avi Kivity 2011-12-27 21:30 ` Peter Maydell 2 siblings, 1 reply; 31+ messages in thread From: Avi Kivity @ 2011-12-27 14:13 UTC (permalink / raw) To: Peter Maydell Cc: Mitsyanko Igor, e.voevodin, Juan Quintela, qemu-devel, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee On 12/26/2011 04:58 PM, Peter Maydell wrote: > > > > void sd_enable(SDState *sd, int enable) > > { > > - sd->enable = enable; > > + sd->enable = enable ? true : false; > > This kind of thing is why I don't like bool :-) /me leaps to bool's defence: sd->enable = enable should work just fine. The assignment is equivalent to sd->enable = enable != 0, as conversion to bool coerces the value to true/false. -- error compiling committee.c: too many arguments to function ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [Qemu-devel] [PATCH 2/3] hw/sd.c: add SD card save/load support 2011-12-27 14:13 ` Avi Kivity @ 2011-12-27 21:30 ` Peter Maydell 2011-12-28 9:50 ` Avi Kivity 0 siblings, 1 reply; 31+ messages in thread From: Peter Maydell @ 2011-12-27 21:30 UTC (permalink / raw) To: Avi Kivity Cc: Mitsyanko Igor, e.voevodin, Juan Quintela, qemu-devel, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee On 27 December 2011 14:13, Avi Kivity <avi@redhat.com> wrote: > On 12/26/2011 04:58 PM, Peter Maydell wrote: >> > void sd_enable(SDState *sd, int enable) >> > { >> > - sd->enable = enable; >> > + sd->enable = enable ? true : false; >> >> This kind of thing is why I don't like bool :-) > > /me leaps to bool's defence: > > sd->enable = enable should work just fine. This is true, but the code snippet also illustrates that it sits oddly to have the internal state variable be bool when the external facing function's API is clearly using the traditional C style of int-for-booleans. Plus 'bool' gives me C++ flashbacks :-) -- PMM ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [Qemu-devel] [PATCH 2/3] hw/sd.c: add SD card save/load support 2011-12-27 21:30 ` Peter Maydell @ 2011-12-28 9:50 ` Avi Kivity 0 siblings, 0 replies; 31+ messages in thread From: Avi Kivity @ 2011-12-28 9:50 UTC (permalink / raw) To: Peter Maydell Cc: Mitsyanko Igor, e.voevodin, Juan Quintela, qemu-devel, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee On 12/27/2011 11:30 PM, Peter Maydell wrote: > On 27 December 2011 14:13, Avi Kivity <avi@redhat.com> wrote: > > On 12/26/2011 04:58 PM, Peter Maydell wrote: > >> > void sd_enable(SDState *sd, int enable) > >> > { > >> > - sd->enable = enable; > >> > + sd->enable = enable ? true : false; > >> > >> This kind of thing is why I don't like bool :-) > > > > /me leaps to bool's defence: > > > > sd->enable = enable should work just fine. > > This is true, but the code snippet also illustrates that it sits > oddly to have the internal state variable be bool when the external > facing function's API is clearly using the traditional C style of > int-for-booleans. We should change those too. bool is self-documenting. > Plus 'bool' gives me C++ flashbacks :-) And QOM doesn't? How about glue(glue(glue(cirrus_colorexpand_pattern_transp_, ROP_NAME), _),DEPTH)? -- error compiling committee.c: too many arguments to function ^ permalink raw reply [flat|nested] 31+ messages in thread
* [Qemu-devel] [PATCH 3/3] hw/: Introduce spec. ver. 2.00 compliant SD host controller 2011-12-26 10:03 [Qemu-devel] [PATCH 0/3] Improve SD controllers emulation Mitsyanko Igor 2011-12-26 10:03 ` [Qemu-devel] [PATCH 1/3] vmstate: introduce calc_size VMStateField Mitsyanko Igor 2011-12-26 10:03 ` [Qemu-devel] [PATCH 2/3] hw/sd.c: add SD card save/load support Mitsyanko Igor @ 2011-12-26 10:03 ` Mitsyanko Igor 2011-12-26 11:35 ` malc 2011-12-28 12:08 ` [Qemu-devel] [PATCH V2 0/3] Improve SD controllers emulation Mitsyanko Igor 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 0/5] Improve SD controllers emulation Mitsyanko Igor 4 siblings, 1 reply; 31+ messages in thread From: Mitsyanko Igor @ 2011-12-26 10:03 UTC (permalink / raw) To: qemu-devel Cc: Mitsyanko Igor, e.voevodin, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee This patch adds implementation of "SD specification version 2.00" compliant SD host controller. Also it provides interface to implement SoC-specific controllers on top of this specification-compliant one. Signed-off-by: Mitsyanko Igor <i.mitsyanko@samsung.com> --- Makefile.target | 1 + hw/sdhc_ver2.c | 1579 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/sdhc_ver2.h | 327 ++++++++++++ 3 files changed, 1907 insertions(+), 0 deletions(-) create mode 100644 hw/sdhc_ver2.c create mode 100644 hw/sdhc_ver2.h diff --git a/Makefile.target b/Makefile.target index 3261383..79c33ac 100644 --- a/Makefile.target +++ b/Makefile.target @@ -358,6 +358,7 @@ obj-arm-y += vexpress.o obj-arm-y += strongarm.o obj-arm-y += collie.o obj-arm-y += pl041.o lm4549.o +obj-arm-y += sdhc_ver2.o obj-sh4-y = shix.o r2d.o sh7750.o sh7750_regnames.o tc58128.o obj-sh4-y += sh_timer.o sh_serial.o sh_intc.o sh_pci.o sm501.o diff --git a/hw/sdhc_ver2.c b/hw/sdhc_ver2.c new file mode 100644 index 0000000..9ba224b --- /dev/null +++ b/hw/sdhc_ver2.c @@ -0,0 +1,1579 @@ +/* + * SD host controller (SD Host Controller Simplified + * Specification Version 2.00 compliant) emulation + * + * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. + * All rights reserved. + * Contributed by Mitsyanko Igor <i.mitsyanko@samsung.com> + * + * Based on MMC controller for Samsung S5PC1xx-based board emulation + * by Alexey Merkulov and Vladimir Monakhov. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "hw.h" +#include "block_int.h" +#include "blockdev.h" +#include "qemu-timer.h" +#include "sdhc_ver2.h" + +/* host controller debug messages */ +#define SDHC_DEBUG 0 + +#if SDHC_DEBUG == 0 + #define DPRINT_L1(fmt, args...) do { } while (0) + #define DPRINT_L2(fmt, args...) do { } while (0) + #define DPRINT_ERROR(fmt, args...) do { } while (0) +#elif SDHC_DEBUG == 1 + #define DPRINT_L1(fmt, args...) \ + do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0) + #define DPRINT_L2(fmt, args...) do { } while (0) + #define DPRINT_ERROR(fmt, args...) \ + do {fprintf(stderr, "QEMU SDHC ERROR: "fmt, ## args); } while (0) +#else + #define DPRINT_L1(fmt, args...) \ + do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0) + #define DPRINT_L2(fmt, args...) \ + do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0) + #define DPRINT_ERROR(fmt, args...) \ + do {fprintf(stderr, "QEMU SDHC ERROR: "fmt, ## args); } while (0) +#endif + +/* Default SD/MMC host controller features information, which will be + * presented in CAPABILITIES register of generic SD host controller at reset. + * If not stated otherwise: + * 0 - not supported, 1 - supported, other - prohibited. + */ +#define SDHC_CAPAB_64BITBUS 0ul /* 64-bit System Bus Support */ +#define SDHC_CAPAB_18V 1ul /* Voltage support 1.8v */ +#define SDHC_CAPAB_30V 0ul /* Voltage support 3.0v */ +#define SDHC_CAPAB_33V 1ul /* Voltage support 3.3v */ +#define SDHC_CAPAB_SUSPRESUME 0ul /* Suspend/resume support */ +#define SDHC_CAPAB_SDMA 1ul /* SDMA support */ +#define SDHC_CAPAB_HIGHSPEED 1ul /* High speed support */ +#define SDHC_CAPAB_ADMA 1ul /* ADMA2 support */ +/* Maximum host controller R/W buffers size + * Possible values: 512, 1024, 2048 bytes */ +#define SDHC_CAPAB_MAXBLOCKLENGTH 512ul +/* Maximum clock frequency for SDclock in MHz + * value in range 10-63 MHz, 0 - not defined */ +#define SDHC_CAPAB_BASECLKFREQ 0ul +#define SDHC_CAPAB_TOUNIT 1ul /* Timeout clock unit 0 - kHz, 1 - MHz */ +/* Timeout clock frequency 1-63, 0 - not defined */ +#define SDHC_CAPAB_TOCLKFREQ 0ul + +/* Now check all parameters and calculate CAPABILITIES REGISTER value */ +#if SDHC_CAPAB_64BITBUS > 1 || SDHC_CAPAB_18V > 1 || SDHC_CAPAB_30V > 1 ||\ + SDHC_CAPAB_33V > 1 || SDHC_CAPAB_SUSPRESUME > 1 || SDHC_CAPAB_SDMA > 1 ||\ + SDHC_CAPAB_HIGHSPEED > 1 || SDHC_CAPAB_ADMA > 1 || SDHC_CAPAB_TOUNIT > 1 +#error Capabilities features SDHC_CAPAB_x must have value 0 or 1! +#endif + +#if SDHC_CAPAB_MAXBLOCKLENGTH == 512 +#define MAX_BLOCK_LENGTH 0ul +#elif SDHC_CAPAB_MAXBLOCKLENGTH == 1024 +#define MAX_BLOCK_LENGTH 1ul +#elif SDHC_CAPAB_MAXBLOCKLENGTH == 2048 +#define MAX_BLOCK_LENGTH 2ul +#else +#error Max host controller block size can have value 512, 1024 or 2048 only! +#endif + +#if (SDHC_CAPAB_BASECLKFREQ > 0 && SDHC_CAPAB_BASECLKFREQ < 10) || \ + SDHC_CAPAB_BASECLKFREQ > 63 +#error SDclock frequency can have value in range 0, 10-63 only! +#endif + +#if SDHC_CAPAB_TOCLKFREQ > 63 +#error Timeout clock frequency can have value in range 0-63 only! +#endif + +#define SDHC_CAPAB_REG_DEFAULT \ + ((SDHC_CAPAB_64BITBUS<<28)|(SDHC_CAPAB_18V<<26)|\ + (SDHC_CAPAB_30V<<25)|(SDHC_CAPAB_33V<<24)|(SDHC_CAPAB_SUSPRESUME<<23)|\ + (SDHC_CAPAB_SDMA<<22)|(SDHC_CAPAB_HIGHSPEED<<21)|(SDHC_CAPAB_ADMA<<19)|\ + (MAX_BLOCK_LENGTH<<16)|(SDHC_CAPAB_BASECLKFREQ<<8)|(SDHC_CAPAB_TOUNIT<<7)|\ + (SDHC_CAPAB_TOCLKFREQ)) + +static void sdhcv2_transfer_complete_irq(void *opaque) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + + /* free data transfer line */ + s->prnsts &= ~(SDHC_DOING_READ | SDHC_DOING_WRITE | + SDHC_DAT_LINE_ACTIVE | SDHC_DATA_INHIBIT | + SDHC_SPACE_AVAILABLE | SDHC_DATA_AVAILABLE); + + if (s->norintstsen & SDHC_NISEN_TRSCMP) { + s->norintsts |= SDHC_NIS_TRSCMP; + } + s->slotint |= (s->norintsigen & s->norintsts) ? 1 : 0; + qemu_set_irq(s->irq, s->norintsigen & s->norintsts); +} + +/* raise command response received interrupt */ +void sdhcv2_raise_response_recieved_irq(SDHCv2State *s) +{ + DPRINT_L2("raise IRQ response\n"); + + if (s->norintstsen & SDHC_NISEN_CMDCMP) { + s->norintsts |= SDHC_NIS_CMDCMP; + } + + DPRINT_L2("Interrupt request %s\n", (s->norintsts & s->norintsigen) || + (s->errintsts & s->errintsigen) ? "raised" : "lowered"); + s->slotint |= ((s->norintsts & s->norintsigen) || + (s->errintsts & s->errintsigen)) ? 1 : 0; + qemu_set_irq(s->irq, (s->norintsts & s->norintsigen) || + (s->errintsts & s->errintsigen)); +} + +static void sdhcv2_raise_insertion_irq(void *opaque) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + bool int_raised = false; + + if (s->norintsts & SDHC_NIS_REMOVE) { + qemu_mod_timer(s->insert_timer, + qemu_get_clock_ns(vm_clock) + SDHC_INSERTION_DELAY); + } else { + s->prnsts = 0x1ff0000; + if (s->norintstsen & SDHC_NISEN_INSERT) { + s->norintsts |= SDHC_NIS_INSERT; + int_raised = (s->norintsigen & SDHC_NORINTSIG_INSERT) || + (s->wakcon & SDHC_WKUP_ON_INSERT); + s->slotint |= (int_raised ? 1 : 0); + } + qemu_set_irq(s->irq, int_raised); + } +} + +static void sdhcv2_insert_eject_cb(void *opaque, int irq, int level) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + DPRINT_L1("Card state changed: %s!\n", level ? "insert" : "eject"); + bool int_raised = false; + + if (s->norintsts & SDHC_NIS_REMOVE) { + if (level) { + DPRINT_L2("Change card state: timer set!\n"); + qemu_mod_timer(s->insert_timer, + qemu_get_clock_ns(vm_clock) + SDHC_INSERTION_DELAY); + } + } else { + if (level) { + s->prnsts = 0x1ff0000; + if (s->norintstsen & SDHC_NISEN_INSERT) { + s->norintsts |= SDHC_NIS_INSERT; + int_raised = (s->norintsigen & SDHC_NORINTSIG_INSERT) || + (s->wakcon & SDHC_WKUP_ON_INSERT); + s->slotint |= (int_raised ? 1 : 0); + } + } else { + s->prnsts = 0x1fa0000; + s->pwrcon &= ~SDHC_POWER_ON; + s->clkcon &= ~SDHC_CLOCK_SDCLK_EN; + if (s->norintstsen & SDHC_NISEN_REMOVE) { + s->norintsts |= SDHC_NIS_REMOVE; + int_raised = (s->norintsigen & SDHC_NORINTSIG_REMOVE) || + (s->wakcon & SDHC_WKUP_ON_REMOVE); + s->slotint |= (int_raised ? 1 : 0); + } + } + qemu_set_irq(s->irq, int_raised); + } +} + +static void sdhcv2_card_readonly_cb(void *opaque, int irq, int level) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + level ? (s->prnsts &= ~SDHC_WRITE_PROTECT) : + (s->prnsts |= SDHC_WRITE_PROTECT); +} + +void sdhcv2_reset(SDHCv2State *s) +{ + unsigned long begin = (unsigned long)s + offsetof(SDHCv2State, sdmasysad); + unsigned long len = + ((unsigned long)s + offsetof(SDHCv2State, capareg)) - begin; + + /* Set all registers to 0. Capabilities registers are not cleared + * and assumed to always preserve their value, given to them during + * initialization */ + memset((void *)begin, 0, len); + + s->card ? (s->prnsts = 0x1ff0000) : (s->prnsts = 0x1fa0000); + s->data_count = 0; + s->stoped_state = sdhc_not_stoped; +} + +void sdhcv2_send_command(SDHCv2State *s) +{ + SDRequest request; + uint8_t response[16]; + int rlen; + s->errintsts = 0; + s->acmd12errsts = 0; + if (!s->card) { + goto error; + } + + request.cmd = s->cmdreg >> 8; + request.arg = s->argument; + DPRINT_L1("Sending command %u with argument %08x\n", + request.cmd, request.arg); + rlen = sd_do_command(s->card, &request, response); + if (rlen < 0) { + goto error; + } + if ((s->cmdreg & SDHC_CMD_RESPONSE) != 0) { +#define RWORD(n) ((n >= 0 ? (response[n] << 24) : 0) \ + | (response[n + 1] << 16) \ + | (response[n + 2] << 8) \ + | response[n + 3]) + + if ((rlen == 0) || (rlen != 4 && rlen != 16)) { + goto error; + } + + s->rspreg[0] = RWORD(0); + if (rlen == 4) { + s->rspreg[1] = s->rspreg[2] = s->rspreg[3] = 0; + } else { + s->rspreg[0] = RWORD(11); + s->rspreg[1] = RWORD(7); + s->rspreg[2] = RWORD(3); + s->rspreg[3] = RWORD(-1); + } +#undef RWORD + DPRINT_L1("Response received:\n RSPREG[127..96]=0x%08x, RSPREG[95..64]=" + "0x%08x,\n RSPREG[63..32]=0x%08x, RSPREG[31..0]=0x%08x\n", + s->rspreg[3], s->rspreg[2], s->rspreg[1], s->rspreg[0]); + } + return; + +error: + DPRINT_ERROR("Timeout waiting for command response\n"); + if (s->errintstsen & SDHC_EISEN_CMDTIMEOUT) { + s->errintsts |= SDHC_EIS_CMDTIMEOUT; + s->norintsts |= SDHC_NIS_ERR; + } +} + +void sdhcv2_do_transfer_complete(SDHCv2State *s) +{ + /* Automatically send CMD12 to stop transfer if AutoCMD12 enabled */ + if ((s->trnmod & SDHC_TRNS_ACMD12) != 0) { + SDRequest request; + uint8_t response[16]; + + request.cmd = 0x0C; + request.arg = 0; + DPRINT_L1("Automatically issue CMD%d %08x\n", request.cmd, request.arg); + sd_do_command(s->card, &request, response); + /* Auto CMD12 response goes to the upper Response register */ + s->rspreg[3] = (response[0] << 24) | (response[1] << 16) | + (response[2] << 8) | response[3]; + } + /* pend a timer which will raise a transfer complete irq */ + qemu_mod_timer(s->transfer_complete_timer, + qemu_get_clock_ns(vm_clock) + 1); +} + +/* + * Programmed i/o data transfer + */ + +/* Fill host controller's read buffer with BLKSIZE bytes of data from card */ +static void sdhcv2_read_block_from_card(void *opaque) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + int index = 0; + + if ((s->trnmod & SDHC_TRNS_MULTI) && + (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0)) { + return; + } + + for (index = 0; index < (s->blksize & 0x0fff); index++) { + s->fifo_buffer[index] = sd_read_data(s->card); + } + + /* New data now available for READ through Buffer Port Register */ + s->prnsts |= SDHC_DATA_AVAILABLE; + if (s->norintstsen & SDHC_NISEN_RBUFRDY) { + s->norintsts |= SDHC_NIS_RBUFRDY; + } + + /* Clear DAT line active status if that was the last block */ + if ((s->trnmod & SDHC_TRNS_MULTI) == 0 || + ((s->trnmod & SDHC_TRNS_MULTI) && s->blkcnt == 1)) { + s->prnsts &= ~SDHC_DAT_LINE_ACTIVE; + } + + /* If stop at block gap request was set and it's not the last block of + * data - generate Block Event interrupt */ + if (s->stoped_state == sdhc_gap_read && (s->trnmod & SDHC_TRNS_MULTI) && + s->blkcnt != 1) { + s->prnsts &= ~SDHC_DAT_LINE_ACTIVE; + if (s->norintstsen & SDHC_EISEN_BLKGAP) { + s->norintsts |= SDHC_EIS_BLKGAP; + } + } + + s->slotint |= (s->norintsts & s->norintsigen) ? 1 : 0; + qemu_set_irq(s->irq, s->norintsts & s->norintsigen); +} + +/* Read @size byte of data from host controller @s BUFFER DATA PORT register */ +uint32_t sdhcv2_read_dataport(SDHCv2State *s, unsigned size) +{ + uint32_t value = 0; + int i; + + /* first check that a valid data exists in host controller input buffer */ + if ((s->prnsts & SDHC_DATA_AVAILABLE) == 0) { + DPRINT_ERROR("Trying to read from empty buffer\n"); + return 0; + } + + for (i = 0; i < size; i++) { + value |= s->fifo_buffer[s->data_count] << i * 8; + s->data_count++; + /* check if we've read all valid data (blksize bytes) from buffer */ + if ((s->data_count) >= (s->blksize & 0x0fff)) { + DPRINT_L2("All %u bytes of data have been read from input buffer\n", + s->data_count); + s->prnsts &= ~SDHC_DATA_AVAILABLE; /* no more data in a buffer */ + s->data_count = 0; /* next buff read must start at position [0] */ + + if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) { + s->blkcnt--; + } + + /* if that was the last block of data */ + if ((s->trnmod & SDHC_TRNS_MULTI) == 0 || + ((s->trnmod & SDHC_TRNS_MULTI) && + (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0))) { + sdhcv2_do_transfer_complete(s); + } else if (s->stoped_state == sdhc_gap_read && + !(s->prnsts & SDHC_DAT_LINE_ACTIVE)) { + /* stop at gap request */ + sdhcv2_transfer_complete_irq(s); + } else { /* if there are more data, read next block from card */ + qemu_mod_timer(s->read_buffer_timer, + qemu_get_clock_ns(vm_clock) + SDHC_READ_BUFFER_DELAY); + } + break; + } + } + + return value; +} + +/* Write data from host controller FIFO to card */ +static void sdhcv2_write_block_to_card(void *opaque) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + int index = 0; + + if (s->prnsts & SDHC_SPACE_AVAILABLE) { + if (s->norintstsen & SDHC_NISEN_WBUFRDY) { + s->norintsts |= SDHC_NIS_WBUFRDY; + } + s->slotint |= (s->norintsts & s->norintsigen) ? 1 : 0; + qemu_set_irq(s->irq, s->norintsigen & s->norintsts); + return; + } + + if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) { + if (s->blkcnt == 0) { + return; + } else { + s->blkcnt--; + } + } + + for (index = 0; index < (s->blksize & 0x0fff); index++) { + sd_write_data(s->card, s->fifo_buffer[index]); + } + + /* Next data can be written through BUFFER DATORT register */ + s->prnsts |= SDHC_SPACE_AVAILABLE; + if (s->norintstsen & SDHC_NISEN_WBUFRDY) { + s->norintsts |= SDHC_NIS_WBUFRDY; + } + + /* Finish transfer if that was the last block of data */ + if ((s->trnmod & SDHC_TRNS_MULTI) == 0 || + ((s->trnmod & SDHC_TRNS_MULTI) && + (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0))) { + sdhcv2_do_transfer_complete(s); + } + + /* Generate Block Gap Event if requested and if not the last block */ + if (s->stoped_state == sdhc_gap_write && (s->trnmod & SDHC_TRNS_MULTI) && + s->blkcnt > 0) { + s->prnsts &= ~SDHC_DOING_WRITE; + if (s->norintstsen & SDHC_EISEN_BLKGAP) { + s->norintsts |= SDHC_EIS_BLKGAP; + } + qemu_mod_timer(s->transfer_complete_timer, + qemu_get_clock_ns(vm_clock) + 1); + } + + s->slotint |= (s->norintsts & s->norintsigen) ? 1 : 0; + qemu_set_irq(s->irq, s->norintsigen & s->norintsts); +} + +/* Write @size bytes of @value data to host controller @s Buffer Data Port + * register */ +void sdhcv2_write_dataport(SDHCv2State *s, uint32_t value, unsigned size) +{ + unsigned i; + + /* Check that there is free space left in a buffer */ + if (!(s->prnsts & SDHC_SPACE_AVAILABLE)) { + DPRINT_ERROR("Can't write to data buffer: buffer full\n"); + return; + } + + for (i = 0; i < size; i++) { + s->fifo_buffer[s->data_count] = value & 0xFF; + s->data_count++; + value >>= 8; + if (s->data_count >= (s->blksize & 0x0fff)) { + DPRINT_L2("write buffer filled with %u bytes of data\n", + s->data_count); + s->data_count = 0; + s->prnsts &= ~SDHC_SPACE_AVAILABLE; + if (s->prnsts & SDHC_DOING_WRITE) { + qemu_mod_timer(s->write_buffer_timer, + qemu_get_clock_ns(vm_clock) + SDHC_WRITE_BUFFER_DELAY); + } + } + } +} + +/* + * Single DMA data transfer + */ + +/* Multi block SDMA transfer */ +void sdhcv2_sdma_transfer_multi_blocks(SDHCv2State *s) +{ + uint32_t datacnt; + bool page_aligned = false; + int n; + int is_read = (s->trnmod & SDHC_TRNS_READ) != 0; + uint32_t boundary_chk = 1 << (((s->blksize & 0xf000) >> 12) + 12); + uint32_t boundary_count = boundary_chk - (s->sdmasysad % boundary_chk); + + /* XXX: Some sd/mmc drivers (for example, u-boot-slp) do not account for + * possible stop at page boundary if initial address is not page aligned, + * allow them to work properly */ + if ((s->sdmasysad % boundary_chk) == 0) { + page_aligned = true; + } + + if (is_read) { + s->prnsts |= SDHC_DOING_READ | SDHC_DATA_INHIBIT | + SDHC_DAT_LINE_ACTIVE; + } else { + s->prnsts |= SDHC_DOING_WRITE | SDHC_DATA_INHIBIT | + SDHC_DAT_LINE_ACTIVE; + } + + /* Transfer data left from last SDMA transaction loop, after controller + * stopped at DMA buffer boundary. This is required only if transfers + * are not aligned to DMA buffer boundary */ + if (s->data_count) { + datacnt = (s->blksize & 0x0fff) - s->data_count; + if (is_read) { + for (; s->data_count < (s->blksize & 0x0fff); s->data_count++) { + s->fifo_buffer[s->data_count] = sd_read_data(s->card); + } + cpu_physical_memory_write(s->sdmasysad, s->fifo_buffer, datacnt); + } else { + cpu_physical_memory_read(s->sdmasysad, s->fifo_buffer, datacnt); + for (; s->data_count < (s->blksize & 0x0fff); s->data_count++) { + sd_write_data(s->card, s->fifo_buffer[s->data_count]); + } + } + s->blkcnt--; + boundary_count -= datacnt; + s->sdmasysad += datacnt; + s->data_count = 0; + } + + while (s->blkcnt) { + if ((boundary_count < (s->blksize & 0x0fff)) && page_aligned) { + s->data_count = datacnt = boundary_count; + } else { + datacnt = s->blksize & 0x0fff; + s->blkcnt--; + } + + if (is_read) { + for (n = 0; n < datacnt; n++) { + s->fifo_buffer[n] = sd_read_data(s->card); + } + cpu_physical_memory_write(s->sdmasysad, s->fifo_buffer, datacnt); + } else { + cpu_physical_memory_read(s->sdmasysad, s->fifo_buffer, datacnt); + for (n = 0; n < datacnt; n++) { + sd_write_data(s->card, s->fifo_buffer[n]); + } + } + + s->sdmasysad += datacnt; + boundary_count -= datacnt; + + if (boundary_count == 0) { + break; + } + } + + if (s->blkcnt == 0) { + sdhcv2_do_transfer_complete(s); + } else { + if (s->norintstsen & SDHC_NISEN_DMA) { + s->norintsts |= SDHC_NIS_DMA; + } + s->slotint |= (s->norintsts & s->norintsigen) ? 1 : 0; + qemu_set_irq(s->irq, s->norintsigen & s->norintsts); + } +} + +/* single block SDMA transfer */ +void sdhcv2_sdma_transfer_single_block(SDHCv2State *s) +{ + int n; + uint32_t datacnt = s->blksize & 0x0fff; + + if (s->trnmod & SDHC_TRNS_READ) { + for (n = 0; n < datacnt; n++) { + s->fifo_buffer[n] = sd_read_data(s->card); + } + cpu_physical_memory_write(s->sdmasysad, s->fifo_buffer, datacnt); + } else { + cpu_physical_memory_read(s->sdmasysad, s->fifo_buffer, datacnt); + for (n = 0; n < datacnt; n++) { + sd_write_data(s->card, s->fifo_buffer[n]); + } + } + + if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) { + s->blkcnt--; + } + + sdhcv2_do_transfer_complete(s); +} + +/* Advanced DMA data transfer */ +void sdhcv2_start_adma(SDHCv2State *s) +{ + uint32_t datacnt; + int n, length; + uint16_t length_table; + uint8_t attributes; + target_phys_addr_t entry_addr, address; + const bool is_32bit_adma = SDHC_DMA_TYPE(s->hostctl) == SDHC_CTRL_ADMA_32; + uint32_t value; + int pos; + + s->admaerr &= ~SDHC_ADMAERR_LENGTH_MISMATCH; + + while (1) { + address = length_table = length = attributes = 0; + value = n = pos = 0; + + entry_addr = is_32bit_adma ? + (s->admasysaddr & 0xFFFFFFFFull) : s->admasysaddr; + /* fetch next entry from descriptor table */ + cpu_physical_memory_read(entry_addr + 4, (uint8_t *)(&address), + is_32bit_adma ? 4 : 8); + cpu_physical_memory_read(entry_addr + 2, (uint8_t *)(&length_table), 2); + cpu_physical_memory_read(entry_addr, (uint8_t *)(&attributes), 1); + DPRINT_L2("ADMA loop: addr=0x%x, len=%d, attr=%x\n", address, + length_table, attributes); + + if ((attributes & SDHC_ADMA_ATTR_VALID) == 0) { + /* Indicate that error occurred in ST_FDS state */ + s->admaerr &= ~SDHC_ADMAERR_STATE_MASK; + s->admaerr |= SDHC_ADMAERR_STATE_ST_FDS; + + /* Generate ADMA error interrupt */ + if (s->errintstsen & SDHC_EISEN_ADMAERR) { + s->errintsts |= SDHC_EIS_ADMAERR; + s->norintsts |= SDHC_NIS_ERR; + } + s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0; + qemu_set_irq(s->irq, s->errintsigen & s->errintsts); + break; + } + + if (length_table == 0) { + length = 65536; + } else { + length = length_table; + } + /* Address must be aligned */ + address &= (is_32bit_adma ? 0xfffffffc : 0xfffffff8); + + switch (attributes & SDHC_ADMA_ATTR_ACT_MASK) { + case SDHC_ADMA_ATTR_ACT_TRAN: /* data transfer */ + for (;;) { + if (s->data_count) { + datacnt = (s->blksize & 0x0fff) - s->data_count; + s->data_count = 0; + } else { + datacnt = s->blksize & 0x0fff; + } + + length -= datacnt; + + if (length < 0) { + s->data_count = (uint16_t)(-length); + datacnt += length; + } + + while (datacnt) { + if (s->trnmod & SDHC_TRNS_READ) { + value |= (uint32_t)sd_read_data(s->card) << (n * 8); + n++; + if (n == 4) { + cpu_physical_memory_write(address + pos, + (uint8_t *)(&value), 4); + value = 0; + n = 0; + pos += 4; + } + } else { + if (n == 0) { + cpu_physical_memory_read(address + pos, + (uint8_t *)(&value), 4); + n = 4; + pos += 4; + } + sd_write_data(s->card, value & 0xff); + value >>= 8; + n--; + } + datacnt--; + } + + if ((s->trnmod & SDHC_TRNS_BLK_CNT_EN) && + (!s->data_count)) { + s->blkcnt--; + if (s->blkcnt == 0) { + break; + } + } + if (length == 0 || s->data_count) { + break; + } + } + s->admasysaddr += (is_32bit_adma ? 8 : 12); + break; + case SDHC_ADMA_ATTR_ACT_LINK: /* link to next descriptor table */ + s->admasysaddr = address; + DPRINT_L1("ADMA link: admasysaddr=0x%lx\n", s->admasysaddr); + break; + default: + s->admasysaddr += (is_32bit_adma ? 8 : 12); + break; + } + + /* ADMA transfer terminates if blkcnt == 0 or by END attribute */ + if (((s->trnmod & SDHC_TRNS_BLK_CNT_EN) && + (s->blkcnt == 0)) || (attributes & SDHC_ADMA_ATTR_END)) { + DPRINT_L2("ADMA transfer completed\n"); + if (length || ((attributes & SDHC_ADMA_ATTR_END) && + (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && + s->blkcnt != 0) || ((s->trnmod & SDHC_TRNS_BLK_CNT_EN) && + s->blkcnt == 0 && (attributes & SDHC_ADMA_ATTR_END) == 0)) { + DPRINT_ERROR("SD/MMC host ADMA length mismatch\n"); + s->admaerr |= SDHC_ADMAERR_LENGTH_MISMATCH | + SDHC_ADMAERR_STATE_ST_TFR; + if (s->errintstsen & SDHC_EISEN_ADMAERR) { + DPRINT_ERROR("Set ADMA error flag\n"); + s->errintsts |= SDHC_EIS_ADMAERR; + s->norintsts |= SDHC_NIS_ERR; + } + s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0; + qemu_set_irq(s->irq, s->errintsigen & s->errintsts); + } + + sdhcv2_do_transfer_complete(s); + break; + } + + if (attributes & SDHC_ADMA_ATTR_INT) { + DPRINT_L1("ADMA interrupt: admasysaddr=0x%lx\n", s->admasysaddr); + if (s->norintstsen & SDHC_NISEN_DMA) { + s->norintsts |= SDHC_NIS_DMA; + } + s->slotint |= (s->norintsts & s->norintsigen) ? 1 : 0; + qemu_set_irq(s->irq, s->norintsigen & s->norintsts); + break; + } + } +} + +/* Perform data transfer according to controller configuration */ +void sdhcv2_transfer_data(SDHCv2State *s) +{ + if (s->trnmod & SDHC_TRNS_DMA) { + switch (SDHC_DMA_TYPE(s->hostctl)) { + case SDHC_CTRL_SDMA: + if ((s->trnmod & SDHC_TRNS_MULTI) && + (!(s->trnmod & SDHC_TRNS_BLK_CNT_EN) || s->blkcnt == 0)) { + break; + } + + if ((s->blkcnt == 1) || !(s->trnmod & SDHC_TRNS_MULTI)) { + sdhcv2_sdma_transfer_single_block(s); + } else { + s->data_count = 0; + sdhcv2_sdma_transfer_multi_blocks(s); + } + break; + case SDHC_CTRL_ADMA_32: + if (!(s->capareg & SDHC_CAN_DO_ADMA)) { + DPRINT_ERROR("ADMA32 transfer aborted: ADMA not supported as" + "states by Capabilities register\n"); + break; + } + s->data_count = 0; + sdhcv2_start_adma(s); + break; + case SDHC_CTRL_ADMA_64: + if (!(s->capareg & SDHC_CAN_DO_ADMA) || + !(s->capareg & SDHC_64_BIT_BUS_SUPPORT)) { + DPRINT_ERROR("ADMA64 transfer aborted: 64 bit ADMA not " + "supported as states by Capabilities register\n"); + break; + } + s->data_count = 0; + sdhcv2_start_adma(s); + break; + default: + DPRINT_ERROR("Unsupported DMA type in HOSTCTL register\n"); + break; + } + } else { + if ((s->trnmod & SDHC_TRNS_READ) && sd_data_ready(s->card)) { + s->prnsts |= SDHC_DOING_READ | SDHC_DATA_INHIBIT | + SDHC_DAT_LINE_ACTIVE; + s->data_count = 0; + qemu_mod_timer(s->read_buffer_timer, + qemu_get_clock_ns(vm_clock) + SDHC_READ_BUFFER_DELAY); + } else { + s->prnsts |= SDHC_DOING_WRITE | SDHC_DAT_LINE_ACTIVE | + SDHC_SPACE_AVAILABLE | SDHC_DATA_INHIBIT; + s->data_count = 0; + qemu_mod_timer(s->write_buffer_timer, + qemu_get_clock_ns(vm_clock) + SDHC_WRITE_BUFFER_DELAY); + } + } +} + +static inline bool sdhcv2_sdclk_is_active(SDHCv2State *s) +{ + if (SDHC_CLOCK_IS_ON(s->clkcon) && (s->pwrcon & SDHC_POWER_ON)) { + return true; + } + return false; +} + +void sdhcv2_trigger_command_generation(SDHCv2State *s) +{ + if (!sdhcv2_sdclk_is_active(s)) { + DPRINT_ERROR("Command not issued: SDCLK not active\n"); + return; + } + if (SDHC_COMMAND_TYPE(s->cmdreg) == SDHC_CMD_RESUME || + SDHC_COMMAND_TYPE(s->cmdreg) == SDHC_CMD_SUSPEND) { + DPRINT_ERROR("Command not issued: suspend/resume commands not" + "implemented\n"); + return; + } + if ((s->stoped_state || (s->prnsts & SDHC_DATA_INHIBIT)) && + ((s->cmdreg & SDHC_CMD_DATA_PRESENT) || + ((s->cmdreg & SDHC_CMD_RSP_WITH_BUSY) && + !(SDHC_COMMAND_TYPE(s->cmdreg) == SDHC_CMD_ABORT)))) { + DPRINT_ERROR("Command not issued: DAT line is busy\n"); + return; + } + + if (SDHC_COMMAND_TYPE(s->cmdreg) == SDHC_CMD_ABORT && + TRANSFERRING_DATA(s->prnsts)) { + DPRINT_L2("ABORT command issued: data transfer stopped\n"); + qemu_del_timer(s->read_buffer_timer); /* stop reading data */ + qemu_del_timer(s->write_buffer_timer); /* stop writing data */ + qemu_mod_timer(s->transfer_complete_timer, + qemu_get_clock_ns(vm_clock) + 1); + } + + sdhcv2_send_command(s); + sdhcv2_raise_response_recieved_irq(s); + + if (s->blksize == 0 || !(s->cmdreg & SDHC_CMD_DATA_PRESENT)) { + return; + } + sdhcv2_transfer_data(s); +} + +/* The Buffer Data Port register must be accessed by sequential and + * continuous manner. + * @byte_num - byte number [0..3] of BDATAPORT register to access */ +static inline bool +sdhcv2_buff_access_is_sequential(SDHCv2State *s, unsigned byte_num) +{ + if ((s->data_count & 0x3) != byte_num) { + DPRINT_ERROR("Non-sequential access to Buffer Data Port register" + "is prohibited\n"); + return false; + } + return true; +} + +/* Read byte from SD host controller registers map */ +uint8_t sdhcv2_read_1byte(SDHCv2State *s, target_phys_addr_t offset) +{ + switch (offset) { + case SDHC_SYSAD ... SDHC_SYSAD_END: + return (s->sdmasysad >> 8 * (offset - SDHC_SYSAD)) & 0xFF; + case SDHC_BLKSIZE ... SDHC_BLKSIZE_END: + return (s->blksize >> 8 * (offset - SDHC_BLKSIZE)) & 0xFF; + case SDHC_BLKCNT ... SDHC_BLKCNT_END: + return (s->blkcnt >> 8 * (offset - SDHC_BLKCNT)) & 0xFF; + case SDHC_ARGUMENT ... SDHC_ARGUMENT_END: + return (s->argument >> 8 * (offset - SDHC_ARGUMENT)) & 0xFF; + case SDHC_TRNMOD ... SDHC_TRNMOD_END: + return (s->trnmod >> 8 * (offset - SDHC_TRNMOD)) & 0xFF; + case SDHC_CMDREG ... SDHC_CMDREG_END: + return (s->cmdreg >> 8 * (offset - SDHC_CMDREG)) & 0xFF; + case SDHC_RSPREG0 ... SDHC_RSPREG3_END: + { + int i = (offset - SDHC_RSPREG0) >> 2; + return (s->rspreg[i] >> 8 * (offset - SDHC_RSPREG0 - i * 4)) & 0xFF; + } + case SDHC_BDATA ... SDHC_BDATA_END: + if (sdhcv2_buff_access_is_sequential(s, offset - SDHC_BDATA)) { + return sdhcv2_read_dataport(s, 1); + } + return 0; + case SDHC_PRNSTS ... SDHC_PRNSTS_END: + return (s->prnsts >> 8 * (offset - SDHC_PRNSTS)) & 0xFF; + case SDHC_HOSTCTL: + return s->hostctl; + case SDHC_PWRCON: + return s->pwrcon; + case SDHC_BLKGAP: + return s->blkgap; + case SDHC_WAKCON: + return s->wakcon; + case SDHC_CLKCON ... SDHC_CLKCON_END: + return (s->clkcon >> 8 * (offset - SDHC_CLKCON)) & 0xFF; + case SDHC_TIMEOUTCON: + return s->timeoutcon; + case SDHC_SWRST: + return 0; + case SDHC_SLOT_INT_STATUS ... SDHC_SLOT_INT_STATUS_END: + return (s->slotint >> 8 * (offset - SDHC_SLOT_INT_STATUS)) & 0xFF; + case SDHC_NORINTSTS ... SDHC_NORINTSTS_END: + return (s->norintsts >> 8 * (offset - SDHC_NORINTSTS)) & 0xFF; + case SDHC_ERRINTSTS ... SDHC_ERRINTSTS_END: + return (s->errintsts >> 8 * (offset - SDHC_ERRINTSTS)) & 0xFF; + case SDHC_NORINTSTSEN ... SDHC_NORINTSTSEN_END: + return (s->norintstsen >> 8 * (offset - SDHC_NORINTSTSEN)) & 0xFF; + case SDHC_ERRINTSTSEN ... SDHC_ERRINTSTSEN_END: + return (s->errintstsen >> 8 * (offset - SDHC_ERRINTSTSEN)) & 0xFF; + case SDHC_NORINTSIGEN ... SDHC_NORINTSIGEN_END: + return (s->norintsigen >> 8 * (offset - SDHC_NORINTSIGEN)) & 0xFF; + case SDHC_ERRINTSIGEN ... SDHC_ERRINTSIGEN_END: + return (s->errintsigen >> 8 * (offset - SDHC_ERRINTSIGEN)) & 0xFF; + case SDHC_ACMD12ERRSTS ... SDHC_ACMD12ERRSTS_END: + return (s->acmd12errsts >> 8 * (offset - SDHC_ACMD12ERRSTS)) & 0xFF; + case SDHC_CAPAREG ... SDHC_CAPAREG_END: + return (s->capareg >> 8 * (offset - SDHC_CAPAREG)) & 0xFF; + case SDHC_MAXCURR ... SDHC_MAXCURR_END: + return (s->maxcurr >> 8 * (offset - SDHC_MAXCURR)) & 0xFF; + case SDHC_ADMAERR: + return s->admaerr; + case SDHC_ADMASYSADDR ... SDHC_ADMASYSADDR_END: + return (s->admasysaddr >> 8 * (offset - SDHC_ADMASYSADDR)) & 0xFF; + case SDHC_HCVER ... SDHC_HCVER_END: + return (SD_HOST_SPECv2_VERS >> 8 * (offset - SDHC_HCVER)) & 0xFF; + default: + DPRINT_ERROR("bad byte read offset " TARGET_FMT_plx "\n", offset); + return 0; + } +} + +/* Read two bytes from SD host controller registers map */ +uint16_t sdhcv2_read_2byte(SDHCv2State *s, target_phys_addr_t offset) +{ + switch (offset) { + case SDHC_BLKSIZE: + return s->blksize; + case SDHC_BLKCNT: + return s->blkcnt; + case SDHC_TRNMOD: + return s->trnmod; + case SDHC_CMDREG: + return s->cmdreg; + case SDHC_BDATA ... SDHC_BDATA_END: + if (sdhcv2_buff_access_is_sequential(s, offset - SDHC_BDATA)) { + return sdhcv2_read_dataport(s, 2); + } + return 0; + case SDHC_CLKCON: + return s->clkcon; + case SDHC_NORINTSTS: + return s->norintsts; + case SDHC_ERRINTSTS: + return s->errintsts; + case SDHC_NORINTSTSEN: + return s->norintstsen; + case SDHC_ERRINTSTSEN: + return s->errintstsen; + case SDHC_NORINTSIGEN: + return s->norintsigen; + case SDHC_ERRINTSIGEN: + return s->errintsigen; + case SDHC_ACMD12ERRSTS: + return s->acmd12errsts; + case SDHC_SLOT_INT_STATUS: + return s->slotint; + case SDHC_HCVER: + return SD_HOST_SPECv2_VERS; + } + /* Try to read 2 bytes one by one */ + return (sdhcv2_read_1byte(s, offset + 1) << 8) | + sdhcv2_read_1byte(s, offset); +} + +/* Read four bytes from SD host controller registers map */ +uint32_t sdhcv2_read_4byte(SDHCv2State *s, target_phys_addr_t offset) +{ + switch (offset) { + case SDHC_SYSAD: + return s->sdmasysad; + case SDHC_ARGUMENT: + return s->argument; + case SDHC_RSPREG0: + return s->rspreg[0]; + case SDHC_RSPREG1: + return s->rspreg[1]; + case SDHC_RSPREG2: + return s->rspreg[2]; + case SDHC_RSPREG3: + return s->rspreg[3]; + case SDHC_BDATA: + if (sdhcv2_buff_access_is_sequential(s, 0)) { + return sdhcv2_read_dataport(s, 4); + } + return 0; + case SDHC_PRNSTS: + return s->prnsts; + case SDHC_CAPAREG: + return s->capareg; + case SDHC_MAXCURR: + return s->maxcurr; + case SDHC_ADMASYSADDR: + return s->admasysaddr; + } + /* Try to split one 4-bytes read into two 2-bytes reads */ + return (sdhcv2_read_2byte(s, offset + 2) << 16) | + sdhcv2_read_2byte(s, offset); +} + +static void sdhcv2_update_slotint(SDHCv2State *s) +{ + if (!(s->norintsts & s->norintsigen) && + !(s->errintsts & s->errintsigen) && + !((s->norintsts & SDHC_NIS_INSERT) && + (s->wakcon & SDHC_WKUP_ON_INSERT)) && + !((s->norintsts & SDHC_NIS_REMOVE) && + (s->wakcon & SDHC_WKUP_ON_REMOVE))) { + s->slotint = 0; + } else { + s->slotint = 1; + } +} + +/* Write byte to SD host controller registers map */ +void sdhcv2_write_1byte(SDHCv2State *s, target_phys_addr_t offset, + uint32_t value) +{ + int off; + + switch (offset) { + case SDHC_SYSAD ... (SDHC_SYSAD_END - 1): + off = 8 * (offset - SDHC_SYSAD); + s->sdmasysad = (s->sdmasysad & ~(0xFF << off)) | (value << off); + break; + case SDHC_SYSAD_END: + /* Writing to last byte of sdmasysad register might trigger transfer */ + s->sdmasysad = (s->sdmasysad & ~0xFF000000) | (value << 24); + if (TRANSFERRING_DATA(s->prnsts) && (s->blkcnt != 0) && + (s->blksize != 0) && SDHC_DMA_TYPE(s->hostctl) == SDHC_CTRL_SDMA) { + sdhcv2_sdma_transfer_multi_blocks(s); + } + break; + case SDHC_BLKSIZE ... SDHC_BLKSIZE_END: + off = 8 * (offset - SDHC_BLKSIZE); + if (!TRANSFERRING_DATA(s->prnsts)) { + s->blksize = (s->blksize & ~(0xFF << off)) | (value << off); + } + break; + case SDHC_BLKCNT ... SDHC_BLKCNT_END: + off = 8 * (offset - SDHC_BLKCNT); + if (!TRANSFERRING_DATA(s->prnsts)) { + s->blkcnt = (s->blkcnt & ~(0xFF << off)) | (value << off); + } + break; + case SDHC_ARGUMENT ... SDHC_ARGUMENT_END: + off = 8 * (offset - SDHC_ARGUMENT); + s->argument = (s->argument & ~(0xFF << off)) | (value << off); + break; + case SDHC_TRNMOD ... SDHC_TRNMOD_END: + off = 8 * (offset - SDHC_TRNMOD); + s->trnmod = (s->trnmod & ~(0xFF << off)) | (value << off); + /* DMA can be enabled only if it's supported as indicated by + * capabilities register */ + if (!(s->capareg & SDHC_CAN_DO_DMA)) { + s->trnmod &= ~SDHC_TRNS_DMA; + } + break; + case SDHC_CMDREG: + s->cmdreg = (s->cmdreg & ~(0xFF)) | value; + break; + case SDHC_CMDREG_END: + /* Writing to the upper byte of CMDREG triggers SD command generation */ + s->cmdreg = (s->cmdreg & ~(0xFF00)) | (value << 8); + sdhcv2_trigger_command_generation(s); + break; + case SDHC_BDATA ... SDHC_BDATA_END: + if (sdhcv2_buff_access_is_sequential(s, offset - SDHC_BDATA)) { + sdhcv2_write_dataport(s, value, 1); + } + break; + case SDHC_HOSTCTL: + s->hostctl = value; + break; + case SDHC_PWRCON: + /* Check that voltage is supported */ + off = (value >> 1) & 0x7; + if ((off < 5) || !(s->capareg & (1 << (31 - off)))) { + DPRINT_ERROR("Bus voltage is not supported\n"); + value &= SDHC_POWER_ON; + } + if (!(s->prnsts & SDHC_CARD_PRESENT)) { + value &= SDHC_POWER_ON; + } + s->pwrcon = value; + break; + case SDHC_BLKGAP: + if (value & 0x0C) { + DPRINT_ERROR("Read-Wait and interrupt at block gap functions" + " not implemented\n"); + } + + if ((value & SDHC_STOP_AT_GAP_REQ) && + (s->blkgap & SDHC_STOP_AT_GAP_REQ)) { + break; + } + s->blkgap = value & (SDHC_STOP_AT_GAP_REQ); + + if ((value & SDHC_CONTINUE_REQ) && s->stoped_state && + (s->blkgap & SDHC_STOP_AT_GAP_REQ) == 0) { + if (s->stoped_state == sdhc_gap_read) { + s->prnsts |= SDHC_DAT_LINE_ACTIVE | SDHC_DOING_READ; + qemu_mod_timer(s->read_buffer_timer, + qemu_get_clock_ns(vm_clock) + SDHC_READ_BUFFER_DELAY); + } else { + s->prnsts |= SDHC_DAT_LINE_ACTIVE | SDHC_DOING_WRITE; + qemu_mod_timer(s->write_buffer_timer, + qemu_get_clock_ns(vm_clock) + SDHC_READ_BUFFER_DELAY); + } + s->stoped_state = sdhc_not_stoped; + } else if (!s->stoped_state && (value & SDHC_STOP_AT_GAP_REQ)) { + if (s->prnsts & SDHC_DOING_READ) { + s->stoped_state = sdhc_gap_read; + } else if (s->prnsts & SDHC_DOING_WRITE) { + s->stoped_state = sdhc_gap_write; + } + } + break; + case SDHC_WAKCON: + s->wakcon = value; + break; + case SDHC_CLKCON ... SDHC_CLKCON_END: + s->clkcon = (s->clkcon & ~(0xFF)) | value; + if (SDHC_CLOCK_INT_EN & s->clkcon) { + s->clkcon |= SDHC_CLOCK_INT_STABLE; + } else { + s->clkcon &= ~SDHC_CLOCK_INT_STABLE; + } + if (!(s->prnsts & SDHC_CARD_PRESENT)) { + s->clkcon &= SDHC_CLOCK_SDCLK_EN; + } + break; + case SDHC_TIMEOUTCON: + s->timeoutcon = value & 0x0F; + break; + case SDHC_SWRST: + switch (value) { + case SDHC_RESET_ALL: + sdhcv2_reset(s); + break; + case SDHC_RESET_CMD: + s->prnsts &= ~SDHC_CMD_INHIBIT; + s->norintsts &= ~SDHC_NIS_CMDCMP; + break; + case SDHC_RESET_DATA: + s->data_count = 0; + qemu_del_timer(s->read_buffer_timer); + qemu_del_timer(s->write_buffer_timer); + s->prnsts &= ~(SDHC_SPACE_AVAILABLE | SDHC_DATA_AVAILABLE | + SDHC_DOING_READ | SDHC_DOING_WRITE | + SDHC_DATA_INHIBIT | SDHC_DAT_LINE_ACTIVE); + s->blkgap &= ~(SDHC_STOP_AT_GAP_REQ | SDHC_CONTINUE_REQ); + s->stoped_state = sdhc_not_stoped; + s->norintsts &= ~(SDHC_NIS_WBUFRDY | SDHC_NIS_RBUFRDY | + SDHC_NIS_DMA | SDHC_NIS_TRSCMP | SDHC_NIS_BLKGAP); + break; + } + break; + case SDHC_NORINTSTS: + s->norintsts &= ~value; + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_NORINTSTS_END: + if (s->norintstsen & SDHC_NISEN_CARDINT) { + value &= ~(SDHC_NIS_CARDINT >> 8); + } + s->norintsts &= (s->norintsts & SDHC_NIS_ERR) | ~(value << 8); + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_ERRINTSTS ... SDHC_ERRINTSTS_END: + s->errintsts &= ~(value << 8 * (offset - SDHC_ERRINTSTS)); + if (!(s->errintsts & SDHC_EIS_CMD12ERR)) { + s->acmd12errsts = 0; + } + s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) : + (s->norintsts &= ~SDHC_NIS_ERR); + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_NORINTSTSEN ... SDHC_NORINTSTSEN_END: + off = 8 * (offset - SDHC_NORINTSTSEN); + s->norintstsen = (s->norintstsen & ~(0xFF << off)) | (value << off); + s->norintsts &= s->norintstsen | SDHC_NIS_ERR; + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_ERRINTSTSEN ... SDHC_ERRINTSTSEN_END: + off = 8 * (offset - SDHC_ERRINTSTSEN); + s->errintstsen = (s->errintstsen & ~(0xFF << off)) | (value << off); + s->errintsts &= s->errintstsen; + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_NORINTSIGEN ... SDHC_NORINTSIGEN_END: + off = 8 * (offset - SDHC_NORINTSIGEN); + s->norintsigen = (s->norintsigen & ~(0xFF << off)) | (value << off); + break; + case SDHC_ERRINTSIGEN ... SDHC_ERRINTSIGEN_END: + off = 8 * (offset - SDHC_ERRINTSIGEN); + s->errintsigen = (s->errintsigen & ~(0xFF << off)) | (value << off); + break; + case SDHC_FEERR ... SDHC_FEERR_END: + s->errintsts |= (value << 8 * (offset - SDHC_FEERR)) & s->errintstsen; + s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) : + (s->norintsts &= ~SDHC_NIS_ERR); + s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0; + qemu_set_irq(s->irq, s->errintsigen & s->errintsts); + break; + case SDHC_FEAER ... SDHC_FEAER_END: + s->acmd12errsts |= (value << 8 * (offset - SDHC_FEAER)); + s->acmd12errsts ? (s->errintsts |= SDHC_EIS_CMD12ERR), + (s->norintsts |= SDHC_NIS_ERR) : + (s->errintsts &= ~SDHC_EIS_CMD12ERR); + s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0; + qemu_set_irq(s->irq, s->errintsigen & s->errintsts); + break; + case SDHC_ADMAERR: + s->admaerr = value; + break; + case SDHC_ADMASYSADDR ... SDHC_ADMASYSADDR_END: + off = 8 * (offset - SDHC_ADMASYSADDR); + s->admasysaddr = + (s->admasysaddr & ~(0xFFull << off)) | ((uint64_t)value << off); + break; + default: + DPRINT_ERROR("bad byte write offset " TARGET_FMT_plx ", value=" + "%u(0x%x)\n", offset, value, value); + break; + } +} + +/* Write 2 bytes to SD host controller registers map */ +void sdhcv2_write_2byte(SDHCv2State *s, target_phys_addr_t offset, + uint16_t value) +{ + switch (offset) { + case SDHC_BLKSIZE: + if (!TRANSFERRING_DATA(s->prnsts)) { + s->blksize = value; + } + break; + case SDHC_BLKCNT: + if (!TRANSFERRING_DATA(s->prnsts)) { + s->blkcnt = value; + } + break; + case SDHC_TRNMOD: + /* DMA can be enabled only if it's supported as indicated by + * capabilities register */ + if (!(s->capareg & SDHC_CAN_DO_DMA)) { + value &= ~SDHC_TRNS_DMA; + } + s->trnmod = value; + break; + case SDHC_CMDREG: + s->cmdreg = value; + sdhcv2_trigger_command_generation(s); + break; + case SDHC_BDATA ... SDHC_BDATA_END: + if (sdhcv2_buff_access_is_sequential(s, offset - SDHC_BDATA)) { + sdhcv2_write_dataport(s, value, 2); + } + break; + case SDHC_CLKCON: + s->clkcon = value; + if (SDHC_CLOCK_INT_EN & s->clkcon) { + s->clkcon |= SDHC_CLOCK_INT_STABLE; + } else { + s->clkcon &= ~SDHC_CLOCK_INT_STABLE; + } + break; + case SDHC_NORINTSTS: + if (s->norintstsen & SDHC_NISEN_CARDINT) { + value &= ~SDHC_NIS_CARDINT; + } + s->norintsts &= (s->norintsts & SDHC_NIS_ERR) | ~value; + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_ERRINTSTS: + s->errintsts &= ~value; + s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) : + (s->norintsts &= ~SDHC_NIS_ERR); + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_NORINTSTSEN: + s->norintstsen = value; + s->norintsts &= s->norintstsen | SDHC_NIS_ERR; + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_ERRINTSTSEN: + s->errintstsen = value; + s->errintsts &= s->errintstsen; + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_NORINTSIGEN: + s->norintsigen = value; + break; + case SDHC_ERRINTSIGEN: + s->errintsigen = value; + break; + case SDHC_FEAER: + s->acmd12errsts |= value; + s->acmd12errsts ? (s->errintsts |= SDHC_EIS_CMD12ERR), + (s->norintsts |= SDHC_NIS_ERR) : + (s->errintsts &= ~SDHC_EIS_CMD12ERR); + s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0; + qemu_set_irq(s->irq, s->errintsigen & s->errintsts); + break; + case SDHC_FEERR: + s->errintsts |= value & s->errintstsen; + s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) : + (s->norintsts &= ~SDHC_NIS_ERR); + s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0; + qemu_set_irq(s->irq, s->errintsigen & s->errintsts); + break; + default: + /* Try to split 2-bytes write into two 1-byte writes */ + sdhcv2_write_1byte(s, offset, value & 0xFF); + sdhcv2_write_1byte(s, offset + 1, (value >> 8) & 0xFF); + break; + } +} + +/* Write 4 bytes to SD host controller registers map */ +void sdhcv2_write_4byte(SDHCv2State *s, target_phys_addr_t offset, + uint32_t value) +{ + switch (offset) { + case SDHC_SYSAD: + s->sdmasysad = value; + if (TRANSFERRING_DATA(s->prnsts) && (s->blkcnt != 0) && + (s->blksize != 0) && SDHC_DMA_TYPE(s->hostctl) == SDHC_CTRL_SDMA) { + sdhcv2_sdma_transfer_multi_blocks(s); + } + break; + case SDHC_ARGUMENT: + s->argument = value; + break; + case SDHC_BDATA: + if (sdhcv2_buff_access_is_sequential(s, 0)) { + sdhcv2_write_dataport(s, value, 4); + } + break; + case SDHC_ADMASYSADDR ... SDHC_ADMASYSADDR_END: + { + int off = 8 * (offset - SDHC_ADMASYSADDR); + s->admasysaddr = (s->admasysaddr & ~(0xFFFFFFFFull << off)) | + ((uint64_t)value << off); + break; + } + default: + /* Try to split 4-bytes write into two 2-byte writes */ + sdhcv2_write_2byte(s, offset, value & 0xFFFF); + sdhcv2_write_2byte(s, offset + 2, (value >> 16) & 0xFFFF); + break; + } +} + +void sdhcv2_initialize(SDHCv2State *s) +{ + DriveInfo *bd; + size_t fifo_len; + + switch (SDHC_CAPAB_BLOCKSIZE(s->capareg)) { + case 0: + fifo_len = 512; + break; + case 1: + fifo_len = 1024; + break; + case 2: + fifo_len = 2048; + break; + default: + hw_error("SDHC: unsupported value for maximum block size\n"); + break; + } + s->fifo_buffer = g_malloc0(fifo_len); + + sysbus_init_irq(&s->busdev, &s->irq); + bd = drive_get_next(IF_SD); + + if (bd) { + DPRINT_L1("SD card inserted: name = %s, sectors = %ld\n", + bd->bdrv->device_name, bd->bdrv->total_sectors); + s->card = sd_init(bd->bdrv, 0); + } else { + DPRINT_L1("No SD card\n"); + s->card = sd_init(NULL, 0); + } + s->eject_cb = qemu_allocate_irqs(sdhcv2_insert_eject_cb, s, 1)[0]; + s->ro_cb = qemu_allocate_irqs(sdhcv2_card_readonly_cb, s, 1)[0]; + sd_set_cb(s->card, s->ro_cb, s->eject_cb); + + s->insert_timer = + qemu_new_timer(vm_clock, SCALE_NS, sdhcv2_raise_insertion_irq, s); + + s->read_buffer_timer = + qemu_new_timer(vm_clock, SCALE_NS, sdhcv2_read_block_from_card, s); + + s->write_buffer_timer = + qemu_new_timer(vm_clock, SCALE_NS, sdhcv2_write_block_to_card, s); + + s->transfer_complete_timer = + qemu_new_timer(vm_clock, SCALE_NS, sdhcv2_transfer_complete_irq, s); +} + +typedef struct SDHCv2GenericState { + SDHCv2State hc; + MemoryRegion iomem; +} SDHCv2GenericState; + +static void sdhcv2_generic_reset(DeviceState *d) +{ + sdhcv2_reset(container_of(d, SDHCv2State, busdev.qdev)); +} + +static uint64_t +sdhcv2_generic_read(void *opaque, target_phys_addr_t offset, unsigned size) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + DPRINT_L2("read %u byte: offset " TARGET_FMT_plx "\n", + size, offset); + + switch (size) { + case 1: + return sdhcv2_read_1byte(s, offset); + case 2: + return sdhcv2_read_2byte(s, offset); + case 4: + return sdhcv2_read_4byte(s, offset); + } + return 0; +} + +static void +sdhcv2_generic_write(void *opaque, target_phys_addr_t offset, uint64_t val, + unsigned size) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + DPRINT_L2("write %u byte: offset " TARGET_FMT_plx " = %lu(0x%lx)\n", + size, offset, val, val); + + switch (size) { + case 1: + sdhcv2_write_1byte(s, offset, val); + break; + case 2: + sdhcv2_write_2byte(s, offset, val); + break; + case 4: + sdhcv2_write_4byte(s, offset, val); + break; + } +} + +static const MemoryRegionOps sdhcv2_generic_mmio_ops = { + .read = sdhcv2_generic_read, + .write = sdhcv2_generic_write, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = false + }, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static int sdhcv2_vmstate_get_fifolen(void *opaque, int version_id) +{ + SDHCv2State *sd = (SDHCv2State *)opaque; + switch (SDHC_CAPAB_BLOCKSIZE(sd->capareg)) { + case 0: + return 512; + case 1: + return 1024; + case 2: + return 2048; + default: + hw_error("SDHC: unsupported value for maximum block size\n"); + } +} + +const VMStateDescription sdhcv2_vmstate = { + .name = "sdhcv2", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT_POINTER(card, SDHCv2State, sd_vmstate, SDState *), + VMSTATE_UINT32(sdmasysad, SDHCv2State), + VMSTATE_UINT16(blksize, SDHCv2State), + VMSTATE_UINT16(blkcnt, SDHCv2State), + VMSTATE_UINT32(argument, SDHCv2State), + VMSTATE_UINT16(trnmod, SDHCv2State), + VMSTATE_UINT16(cmdreg, SDHCv2State), + VMSTATE_UINT32_ARRAY(rspreg, SDHCv2State, 4), + VMSTATE_UINT32(prnsts, SDHCv2State), + VMSTATE_UINT8(hostctl, SDHCv2State), + VMSTATE_UINT8(pwrcon, SDHCv2State), + VMSTATE_UINT8(blkgap, SDHCv2State), + VMSTATE_UINT8(wakcon, SDHCv2State), + VMSTATE_UINT16(clkcon, SDHCv2State), + VMSTATE_UINT8(timeoutcon, SDHCv2State), + VMSTATE_UINT8(admaerr, SDHCv2State), + VMSTATE_UINT16(norintsts, SDHCv2State), + VMSTATE_UINT16(errintsts, SDHCv2State), + VMSTATE_UINT16(norintstsen, SDHCv2State), + VMSTATE_UINT16(errintstsen, SDHCv2State), + VMSTATE_UINT16(norintsigen, SDHCv2State), + VMSTATE_UINT16(errintsigen, SDHCv2State), + VMSTATE_UINT16(acmd12errsts, SDHCv2State), + VMSTATE_UINT16(data_count, SDHCv2State), + VMSTATE_UINT16(slotint, SDHCv2State), + VMSTATE_UINT64(admasysaddr, SDHCv2State), + VMSTATE_UINT8(stoped_state, SDHCv2State), + VMSTATE_VBUFFER_CALCSIZE(fifo_buffer, SDHCv2State, + 1, NULL, 0, sdhcv2_vmstate_get_fifolen), + VMSTATE_TIMER(insert_timer, SDHCv2State), + VMSTATE_TIMER(read_buffer_timer, SDHCv2State), + VMSTATE_TIMER(write_buffer_timer, SDHCv2State), + VMSTATE_TIMER(transfer_complete_timer, SDHCv2State), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription sdhcv2_generic_vmstate = { + .name = "sdhcv2_generic", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(hc, SDHCv2GenericState, 1, sdhcv2_vmstate, SDHCv2State), + VMSTATE_END_OF_LIST() + } +}; + +static int sdhcv2_generic_init(SysBusDevice *dev) +{ + SDHCv2State *s = FROM_SYSBUS(SDHCv2State, dev); + SDHCv2GenericState *hc_gen = DO_UPCAST(SDHCv2GenericState, hc, s); + + sdhcv2_initialize(s); + memory_region_init_io(&hc_gen->iomem, &sdhcv2_generic_mmio_ops, s, + "sdhcv2", SDHC_REGISTERS_MAP_SIZE); + sysbus_init_mmio(dev, &hc_gen->iomem); + + return 0; +} + +static SysBusDeviceInfo sdhcv2_generic_info = { + .init = sdhcv2_generic_init, + .qdev.name = "sdhcv2", + .qdev.size = sizeof(SDHCv2GenericState), + .qdev.vmsd = &sdhcv2_generic_vmstate, + .qdev.reset = sdhcv2_generic_reset, + .qdev.props = (Property[]) { + DEFINE_PROP_HEX32("capareg", SDHCv2GenericState, hc.capareg, + SDHC_CAPAB_REG_DEFAULT), + DEFINE_PROP_HEX32("maxcurr", SDHCv2GenericState, hc.maxcurr, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void sdhcv2_register_devices(void) +{ + sysbus_register_withprop(&sdhcv2_generic_info); +} + +device_init(sdhcv2_register_devices) diff --git a/hw/sdhc_ver2.h b/hw/sdhc_ver2.h new file mode 100644 index 0000000..2b13e16 --- /dev/null +++ b/hw/sdhc_ver2.h @@ -0,0 +1,327 @@ +/* + * SD host controller (SD Host Controller Simplified + * Specification Version 2.00 compliant) emulation + * + * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. + * All rights reserved. + * Contributed by Mitsyanko Igor <i.mitsyanko@samsung.com> + * + * Based on MMC controller for Samsung S5PC1xx-based board emulation + * by Alexey Merkulov and Vladimir Monakhov. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SDHC_VER2_H_ +#define SDHC_VER2_H_ + +#include "qemu-common.h" +#include "sysbus.h" +#include "sd.h" + +/* R/W SDMA System Address register 0x0 */ +#define SDHC_SYSAD 0x00 +#define SDHC_SYSAD_END 0x03 + +/* R/W Host DMA Buffer Boundary and Transfer Block Size Register 0x0 */ +#define SDHC_BLKSIZE 0x04 +#define SDHC_BLKSIZE_END 0x05 + +/* R/W Blocks count for current transfer 0x0 */ +#define SDHC_BLKCNT 0x06 +#define SDHC_BLKCNT_END 0x07 + +/* R/W Command Argument Register 0x0 */ +#define SDHC_ARGUMENT 0x08 +#define SDHC_ARGUMENT_END 0x0B + +/* R/W Transfer Mode Setting Register 0x0 */ +#define SDHC_TRNMOD 0x0C +#define SDHC_TRNMOD_END 0x0D +#define SDHC_TRNS_DMA 0x0001 +#define SDHC_TRNS_BLK_CNT_EN 0x0002 +#define SDHC_TRNS_ACMD12 0x0004 +#define SDHC_TRNS_READ 0x0010 +#define SDHC_TRNS_MULTI 0x0020 + +/* R/W Command Register 0x0 */ +#define SDHC_CMDREG 0x0E +#define SDHC_CMDREG_END 0x0F +#define SDHC_CMD_RSP_WITH_BUSY (3 << 0) +#define SDHC_CMD_DATA_PRESENT (1 << 5) +#define SDHC_CMD_SUSPEND (1 << 6) +#define SDHC_CMD_RESUME (1 << 7) +#define SDHC_CMD_ABORT ((1 << 6)|(1 << 7)) +#define SDHC_CMD_TYPE_MASK ((1 << 6)|(1 << 7)) +#define SDHC_COMMAND_TYPE(x) ((x) & SDHC_CMD_TYPE_MASK) + +/* ROC Response Register 0 0x0 */ +#define SDHC_RSPREG0 0x10 +/* ROC Response Register 1 0x0 */ +#define SDHC_RSPREG1 0x14 +/* ROC Response Register 2 0x0 */ +#define SDHC_RSPREG2 0x18 +/* ROC Response Register 3 0x0 */ +#define SDHC_RSPREG3 0x1C +#define SDHC_RSPREG3_END 0x1F + +/* R/W Buffer Data Register 0x0 */ +#define SDHC_BDATA 0x20 +#define SDHC_BDATA_END 0x23 +#define SDHC_BUFFER_END (-1) + +/* R/ROC Present State Register 0x000A0000 */ +#define SDHC_PRNSTS 0x24 +#define SDHC_PRNSTS_END 0x27 +#define SDHC_CMD_INHIBIT 0x00000001 +#define SDHC_DATA_INHIBIT 0x00000002 +#define SDHC_DAT_LINE_ACTIVE 0x00000004 +#define SDHC_DOING_WRITE 0x00000100 +#define SDHC_DOING_READ 0x00000200 +#define SDHC_SPACE_AVAILABLE 0x00000400 +#define SDHC_DATA_AVAILABLE 0x00000800 +#define SDHC_CARD_PRESENT 0x00010000 +#define SDHC_WRITE_PROTECT 0x00080000 +#define TRANSFERRING_DATA(x) \ + ((x) & (SDHC_DOING_READ | SDHC_DOING_WRITE)) + +/* R/W Host control Register 0x0 */ +#define SDHC_HOSTCTL 0x28 +#define SDHC_CTRL_DMA_CHECK_MASK 0x18 +#define SDHC_CTRL_SDMA 0x00 +#define SDHC_CTRL_ADMA_32 0x10 +#define SDHC_CTRL_ADMA_64 0x18 +#define SDHC_DMA_TYPE(x) ((x) & SDHC_CTRL_DMA_CHECK_MASK) + +/* R/W Power Control Register 0x0 */ +#define SDHC_PWRCON 0x29 +#define SDHC_POWER_ON (1 << 0) + +/* R/W Block Gap Control Register 0x0 */ +#define SDHC_BLKGAP 0x2A +#define SDHC_STOP_AT_GAP_REQ 0x01 +#define SDHC_CONTINUE_REQ 0x02 + +/* R/W WakeUp Control Register 0x0 */ +#define SDHC_WAKCON 0x2B +#define SDHC_WKUP_ON_INSERT (1 << 1) +#define SDHC_WKUP_ON_REMOVE (1 << 2) + +/* CLKCON */ +#define SDHC_CLKCON 0x2C +#define SDHC_CLKCON_END 0x2D +#define SDHC_CLOCK_INT_STABLE 0x0002 +#define SDHC_CLOCK_INT_EN 0x0001 +#define SDHC_CLOCK_SDCLK_EN (1 << 2) +#define SDHC_CLOCK_CHK_MASK 0x0007 +#define SDHC_CLOCK_IS_ON(x) \ + (((x) & SDHC_CLOCK_CHK_MASK) == SDHC_CLOCK_CHK_MASK) + +/* R/W Timeout Control Register 0x0 */ +#define SDHC_TIMEOUTCON 0x2E + +/* R/W Software Reset Register 0x0 */ +#define SDHC_SWRST 0x2F +#define SDHC_RESET_ALL 0x01 +#define SDHC_RESET_CMD 0x02 +#define SDHC_RESET_DATA 0x04 + +/* ROC/RW1C Normal Interrupt Status Register 0x0 */ +#define SDHC_NORINTSTS 0x30 +#define SDHC_NORINTSTS_END 0x31 +#define SDHC_NIS_ERR 0x8000 +#define SDHC_NIS_CMDCMP 0x0001 +#define SDHC_NIS_TRSCMP 0x0002 +#define SDHC_NIS_BLKGAP 0x0004 +#define SDHC_NIS_DMA 0x0008 +#define SDHC_NIS_WBUFRDY 0x0010 +#define SDHC_NIS_RBUFRDY 0x0020 +#define SDHC_NIS_INSERT 0x0040 +#define SDHC_NIS_REMOVE 0x0080 +#define SDHC_NIS_CARDINT 0x0100 + +/* ROC/RW1C Error Interrupt Status Register 0x0 */ +#define SDHC_ERRINTSTS 0x32 +#define SDHC_ERRINTSTS_END 0x33 +#define SDHC_EIS_CMDTIMEOUT 0x0001 +#define SDHC_EIS_BLKGAP 0x0004 +#define SDHC_EIS_CMD12ERR 0x0100 +#define SDHC_EIS_ADMAERR 0x0200 + +/* R/W Normal Interrupt Status Enable Register 0x0 */ +#define SDHC_NORINTSTSEN 0x34 +#define SDHC_NORINTSTSEN_END 0x35 +#define SDHC_NISEN_CMDCMP 0x0001 +#define SDHC_NISEN_TRSCMP 0x0002 +#define SDHC_NISEN_DMA 0x0008 +#define SDHC_NISEN_WBUFRDY 0x0010 +#define SDHC_NISEN_RBUFRDY 0x0020 +#define SDHC_NISEN_INSERT 0x0040 +#define SDHC_NISEN_REMOVE 0x0080 +#define SDHC_NISEN_CARDINT 0x0100 + +/* R/W Error Interrupt Status Enable Register 0x0 */ +#define SDHC_ERRINTSTSEN 0x36 +#define SDHC_ERRINTSTSEN_END 0x37 +#define SDHC_EISEN_CMDTIMEOUT 0x0001 +#define SDHC_EISEN_BLKGAP 0x0004 +#define SDHC_EISEN_ADMAERR 0x0200 + +/* R/W Normal Interrupt Signal Enable Register 0x0 */ +#define SDHC_NORINTSIGEN 0x38 +#define SDHC_NORINTSIGEN_END 0x39 +#define SDHC_NORINTSIG_INSERT (1 << 6) +#define SDHC_NORINTSIG_REMOVE (1 << 7) + +/* R/W Error Interrupt Signal Enable Register 0x0 */ +#define SDHC_ERRINTSIGEN 0x3A +#define SDHC_ERRINTSIGEN_END 0x3B + +/* ROC Auto CMD12 error status register 0x0 */ +#define SDHC_ACMD12ERRSTS 0x3C +#define SDHC_ACMD12ERRSTS_END 0x3D + +/* HWInit Capabilities Register 0x05E80080 */ +#define SDHC_CAPAREG 0x40 +#define SDHC_CAPAREG_END 0x43 +#define SDHC_CAN_DO_DMA 0x00400000 +#define SDHC_CAN_DO_ADMA 0x00080000 +#define SDHC_64_BIT_BUS_SUPPORT (1 << 28) +#define SDHC_CAPAB_BLOCKSIZE(x) (((x) >> 16) & 0x3) + +/* HWInit Maximum Current Capabilities Register 0x0 */ +#define SDHC_MAXCURR 0x48 +#define SDHC_MAXCURR_END 0x4B + +/* W Force Event Auto CMD12 Error Interrupt Register 0x0000 */ +#define SDHC_FEAER 0x50 +#define SDHC_FEAER_END 0x51 +/* W Force Event Error Interrupt Register Error Interrupt 0x0000 */ +#define SDHC_FEERR 0x52 +#define SDHC_FEERR_END 0x53 + +/* R/W ADMA Error Status Register 0x00 */ +#define SDHC_ADMAERR 0x54 +#define SDHC_ADMAERR_LENGTH_MISMATCH (1 << 2) +#define SDHC_ADMAERR_STATE_ST_STOP (0 << 0) +#define SDHC_ADMAERR_STATE_ST_FDS (1 << 0) +#define SDHC_ADMAERR_STATE_ST_TFR (3 << 0) +#define SDHC_ADMAERR_STATE_MASK (3 << 0) + +/* R/W ADMA System Address Register 0x00 */ +#define SDHC_ADMASYSADDR 0x58 +#define SDHC_ADMASYSADDR_END 0x5F +#define SDHC_ADMA_ATTR_ACT_TRAN (1 << 5) +#define SDHC_ADMA_ATTR_ACT_LINK (3 << 4) +#define SDHC_ADMA_ATTR_INT (1 << 2) +#define SDHC_ADMA_ATTR_END (1 << 1) +#define SDHC_ADMA_ATTR_VALID (1 << 0) +#define SDHC_ADMA_ATTR_ACT_MASK ((1 << 4)|(1 << 5)) + +/* Slot interrupt status */ +#define SDHC_SLOT_INT_STATUS 0xFC +#define SDHC_SLOT_INT_STATUS_END 0xFD + +/* HWInit Host Controller Version Register 0x0401 */ +#define SDHC_HCVER 0xFE +#define SDHC_HCVER_END 0xFF +#define SD_HOST_SPECv2_VERS 0x2401 + +#define SDHC_REGISTERS_MAP_SIZE 0x100 +#define SDHC_READ_BUFFER_DELAY 1 +#define SDHC_WRITE_BUFFER_DELAY 2 +#define SDHC_INSERTION_DELAY (get_ticks_per_sec()) +#define SDHC_CMD_RESPONSE (3 << 0) + +enum { + sdhc_not_stoped = 0, /* normal SDHC state */ + sdhc_gap_read = 1, /* SDHC stopped at block gap during read operation */ + sdhc_gap_write = 2 /* SDHC stopped at block gap during write operation */ +}; + +/* SD/MMC host controller state */ +typedef struct SDHCv2State { + SysBusDevice busdev; + SDState *card; + + QEMUTimer *insert_timer; /* timer for 'changing' sd card. */ + QEMUTimer *read_buffer_timer; /* read block of data to controller FIFO */ + QEMUTimer *write_buffer_timer; /* write block of data to card from FIFO */ + QEMUTimer *transfer_complete_timer; /* raise transfer complete irq */ + qemu_irq eject_cb; + qemu_irq ro_cb; + qemu_irq irq; + + uint32_t sdmasysad; /* SDMA System Address register */ + uint16_t blksize; /* Host DMA Buff Boundary and Transfer BlkSize Reg */ + uint16_t blkcnt; /* Blocks count for current transfer */ + uint32_t argument; /* Command Argument Register */ + uint16_t trnmod; /* Transfer Mode Setting Register */ + uint16_t cmdreg; /* Command Register */ + uint32_t rspreg[4]; /* Response Registers 0-3 */ + uint32_t prnsts; /* Present State Register */ + uint8_t hostctl; /* Present State Register */ + uint8_t pwrcon; /* Present State Register */ + uint8_t blkgap; /* Block Gap Control Register */ + uint8_t wakcon; /* WakeUp Control Register */ + uint16_t clkcon; /* Command Register */ + uint8_t timeoutcon; /* Timeout Control Register */ + uint8_t admaerr; /* ADMA Error Status Register */ + uint16_t norintsts; /* Normal Interrupt Status Register */ + uint16_t errintsts; /* Error Interrupt Status Register */ + uint16_t norintstsen; /* Normal Interrupt Status Enable Register */ + uint16_t errintstsen; /* Error Interrupt Status Enable Register */ + uint16_t norintsigen; /* Normal Interrupt Signal Enable Register */ + uint16_t errintsigen; /* Error Interrupt Signal Enable Register */ + uint16_t acmd12errsts; /* Auto CMD12 error status register */ + uint16_t slotint; /* Slot interrupt status register */ + uint64_t admasysaddr; /* ADMA System Address Register */ + + uint32_t capareg; /* Capabilities Register */ + uint32_t maxcurr; /* Maximum Current Capabilities Register */ + uint8_t *fifo_buffer; /* SD host i/o FIFO buffer */ + uint16_t data_count; /* current element in FIFO buffer */ + uint8_t stoped_state; /* Current SDHC state */ + /* Buffer Data Port Register - virtual access point to R and W buffers */ + /* Software Reset Register - always reads as 0 */ + /* Force Event Auto CMD12 Error Interrupt Reg - write only */ + /* Force Event Error Interrupt Register- write only */ + /* RO Host Controller Version Register always reads as 0x2401 */ +} SDHCv2State; + +extern const VMStateDescription sdhcv2_vmstate; + +void sdhcv2_initialize(SDHCv2State *s); +void sdhcv2_reset(SDHCv2State *s); +uint8_t sdhcv2_read_1byte(SDHCv2State *s, target_phys_addr_t offset); +uint16_t sdhcv2_read_2byte(SDHCv2State *s, target_phys_addr_t offset); +uint32_t sdhcv2_read_4byte(SDHCv2State *s, target_phys_addr_t offset); +void sdhcv2_write_1byte(SDHCv2State *s, target_phys_addr_t offset, + uint32_t value); +void sdhcv2_write_2byte(SDHCv2State *s, target_phys_addr_t offset, + uint16_t value); +void sdhcv2_write_4byte(SDHCv2State *s, target_phys_addr_t offset, + uint32_t value); +void sdhcv2_send_command(SDHCv2State *s); +void sdhcv2_raise_response_recieved_irq(SDHCv2State *s); +void sdhcv2_trigger_command_generation(SDHCv2State *s); +void sdhcv2_transfer_data(SDHCv2State *s); +void sdhcv2_sdma_transfer_single_block(SDHCv2State *s); +void sdhcv2_sdma_transfer_multi_blocks(SDHCv2State *s); +void sdhcv2_start_adma(SDHCv2State *s); +void sdhcv2_do_transfer_complete(SDHCv2State *s); +uint32_t sdhcv2_read_dataport(SDHCv2State *s, unsigned size); +void sdhcv2_write_dataport(SDHCv2State *s, uint32_t value, unsigned size); + +#endif /* SDHC_VER2_H_ */ -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 31+ messages in thread
* Re: [Qemu-devel] [PATCH 3/3] hw/: Introduce spec. ver. 2.00 compliant SD host controller 2011-12-26 10:03 ` [Qemu-devel] [PATCH 3/3] hw/: Introduce spec. ver. 2.00 compliant SD host controller Mitsyanko Igor @ 2011-12-26 11:35 ` malc 0 siblings, 0 replies; 31+ messages in thread From: malc @ 2011-12-26 11:35 UTC (permalink / raw) To: Mitsyanko Igor Cc: e.voevodin, qemu-devel, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee On Mon, 26 Dec 2011, Mitsyanko Igor wrote: > This patch adds implementation of "SD specification version 2.00" compliant > SD host controller. Also it provides interface to implement SoC-specific > controllers on top of this specification-compliant one. > [..snip..] > + > +void sdhcv2_reset(SDHCv2State *s) > +{ > + unsigned long begin = (unsigned long)s + offsetof(SDHCv2State, sdmasysad); This is wrong, uintptr_t begin = (uintptr_t) &s->sdmasysad; Caveat emptor: i haven't tried to understand what the code tries to accomplish, so maybe there's a way that doesn't involve even the above. [..snip..] -- mailto:av1474@comtv.ru ^ permalink raw reply [flat|nested] 31+ messages in thread
* [Qemu-devel] [PATCH V2 0/3] Improve SD controllers emulation 2011-12-26 10:03 [Qemu-devel] [PATCH 0/3] Improve SD controllers emulation Mitsyanko Igor ` (2 preceding siblings ...) 2011-12-26 10:03 ` [Qemu-devel] [PATCH 3/3] hw/: Introduce spec. ver. 2.00 compliant SD host controller Mitsyanko Igor @ 2011-12-28 12:08 ` Mitsyanko Igor 2011-12-28 12:08 ` [Qemu-devel] [PATCH V2 1/3] vmstate: introduce get_bufsize entry in VMStateField Mitsyanko Igor ` (2 more replies) 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 0/5] Improve SD controllers emulation Mitsyanko Igor 4 siblings, 3 replies; 31+ messages in thread From: Mitsyanko Igor @ 2011-12-28 12:08 UTC (permalink / raw) To: qemu-devel Cc: peter.maydell, e.voevodin, quintela, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee, afaerber Changelog v1->v2: PATCH 1/3: - .calc_size field replaced with .get_bufsize field in VMStateField; - .size_offset removed completely, macros based on it rewritten to use new .get_bufsize field. PATCH 2/3: - all binary variables in SDState now have bool type; - SDState structure rearranged to ensure alignement; - sd_init(), sd_enable() now receive bool; - new version of PATCH 1/3 now used to save wp_groups array; PATCH 3/3: - DMA transfers modified and now operate with data blocks of BLKSIZE only, like real hardware does; - reset procedure optimized; - new version of PATCH 1/3 now used to save fifo_buffer. First patch of this patch set modifies existing VMStateField interface to ease save/restore functionality implementation for device's dynamically allocated buffers. This modification is used later in second patch to implement SD card's VMStateDescription structure. Third patch adds imlementation of new device: SD host controller fully compliant with "SD host controller specification version 2.00". It also uses first patch modifications. Mitsyanko Igor (3): vmstate: introduce get_bufsize entry in VMStateField hw/sd.c: add SD card save/load support hw: Introduce spec. ver. 2.00 compliant SD host controller Makefile.target | 1 + hw/g364fb.c | 7 +- hw/hw.h | 41 +-- hw/m48t59.c | 7 +- hw/mac_nvram.c | 8 +- hw/onenand.c | 7 +- hw/sd.c | 130 +++-- hw/sd.h | 4 +- hw/sdhc_ver2.c | 1569 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/sdhc_ver2.h | 327 ++++++++++++ savevm.c | 10 +- 11 files changed, 2020 insertions(+), 91 deletions(-) create mode 100644 hw/sdhc_ver2.c create mode 100644 hw/sdhc_ver2.h -- 1.7.4.1 ^ permalink raw reply [flat|nested] 31+ messages in thread
* [Qemu-devel] [PATCH V2 1/3] vmstate: introduce get_bufsize entry in VMStateField 2011-12-28 12:08 ` [Qemu-devel] [PATCH V2 0/3] Improve SD controllers emulation Mitsyanko Igor @ 2011-12-28 12:08 ` Mitsyanko Igor 2011-12-28 12:08 ` [Qemu-devel] [PATCH V2 2/3] hw/sd.c: add SD card save/load support Mitsyanko Igor 2011-12-28 12:08 ` [Qemu-devel] [PATCH V2 3/3] hw: Introduce spec. ver. 2.00 compliant SD host controller Mitsyanko Igor 2 siblings, 0 replies; 31+ messages in thread From: Mitsyanko Igor @ 2011-12-28 12:08 UTC (permalink / raw) To: qemu-devel Cc: peter.maydell, Mitsyanko Igor, e.voevodin, quintela, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee, afaerber New get_bufsize field in VMStateField is supposed to help us easily add save/restore support of dynamically allocated buffers in device's states. There are some cases when information about size of dynamically allocated buffer is already presented in specific device's state structure, but in such a form that can not be used with existing VMStateField interface. Currently, we either can get size from another variable in device's state as it is with VMSTATE_VBUFFER_* macros, or we can also multiply value kept in a variable by a constant with VMSTATE_BUFFER_MULTIPLY macro. If we need to perform any other action, we're forced to add additional variable with size information to device state structure with the only intention to use it in VMStateDescription. This approach is not very pretty. Adding extra flags to VMStateFlags enum for every other possible operation with size field seems redundant, and still it would't cover cases when we need to perform a set of operations to get size value. With get_bufsize callback we can calculate size of dynamic array in whichever way we need. We don't need .size_offset field anymore, so we can remove it from VMState Field structure to compensate for extra memory consuption because of get_bufsize addition. Macros VMSTATE_VBUFFER* are modified to use new callback instead of .size_offset. Macro VMSTATE_BUFFER_MULTIPLY and VMFlag VMS_MULTIPLY are removed completely as they are now redundant. Signed-off-by: Mitsyanko Igor <i.mitsyanko@samsung.com> --- hw/g364fb.c | 7 ++++++- hw/hw.h | 41 +++++++---------------------------------- hw/m48t59.c | 7 ++++++- hw/mac_nvram.c | 8 +++++++- hw/onenand.c | 7 ++++++- savevm.c | 10 ++-------- 6 files changed, 34 insertions(+), 46 deletions(-) diff --git a/hw/g364fb.c b/hw/g364fb.c index 34fb08c..1ab36c2 100644 --- a/hw/g364fb.c +++ b/hw/g364fb.c @@ -495,6 +495,11 @@ static int g364fb_post_load(void *opaque, int version_id) return 0; } +static int g364fb_get_vramsize(void *opaque, int version_id) +{ + return ((G364State *)opaque)->vram_size; +} + static const VMStateDescription vmstate_g364fb = { .name = "g364fb", .version_id = 1, @@ -502,7 +507,7 @@ static const VMStateDescription vmstate_g364fb = { .minimum_version_id_old = 1, .post_load = g364fb_post_load, .fields = (VMStateField[]) { - VMSTATE_VBUFFER_UINT32(vram, G364State, 1, NULL, 0, vram_size), + VMSTATE_VBUFFER(vram, G364State, 1, NULL, 0, g364fb_get_vramsize), VMSTATE_BUFFER_UNSAFE(color_palette, G364State, 0, 256 * 3), VMSTATE_BUFFER_UNSAFE(cursor_palette, G364State, 0, 9), VMSTATE_UINT16_ARRAY(cursor, G364State, 512), diff --git a/hw/hw.h b/hw/hw.h index efa04d1..a2a43b6 100644 --- a/hw/hw.h +++ b/hw/hw.h @@ -303,7 +303,6 @@ enum VMStateFlags { VMS_ARRAY_OF_POINTER = 0x040, VMS_VARRAY_UINT16 = 0x080, /* Array with size in uint16_t field */ VMS_VBUFFER = 0x100, /* Buffer with size in int32_t field */ - VMS_MULTIPLY = 0x200, /* multiply "size" field by field_size */ VMS_VARRAY_UINT8 = 0x400, /* Array with size in uint8_t field*/ VMS_VARRAY_UINT32 = 0x800, /* Array with size in uint32_t field*/ }; @@ -315,12 +314,12 @@ typedef struct { size_t start; int num; size_t num_offset; - size_t size_offset; const VMStateInfo *info; enum VMStateFlags flags; const VMStateDescription *vmsd; int version_id; bool (*field_exists)(void *opaque, int version_id); + int (*get_bufsize)(void *opaque, int version_id); } VMStateField; typedef struct VMStateSubsection { @@ -584,34 +583,11 @@ extern const VMStateInfo vmstate_info_unused_buffer; .offset = vmstate_offset_buffer(_state, _field) + _start, \ } -#define VMSTATE_BUFFER_MULTIPLY(_field, _state, _version, _test, _start, _field_size, _multiply) { \ +#define VMSTATE_VBUFFER(_field, _state, _version, _test, _start, _get_bufsize) { \ .name = (stringify(_field)), \ .version_id = (_version), \ .field_exists = (_test), \ - .size_offset = vmstate_offset_value(_state, _field_size, uint32_t),\ - .size = (_multiply), \ - .info = &vmstate_info_buffer, \ - .flags = VMS_VBUFFER|VMS_MULTIPLY, \ - .offset = offsetof(_state, _field), \ - .start = (_start), \ -} - -#define VMSTATE_VBUFFER(_field, _state, _version, _test, _start, _field_size) { \ - .name = (stringify(_field)), \ - .version_id = (_version), \ - .field_exists = (_test), \ - .size_offset = vmstate_offset_value(_state, _field_size, int32_t),\ - .info = &vmstate_info_buffer, \ - .flags = VMS_VBUFFER|VMS_POINTER, \ - .offset = offsetof(_state, _field), \ - .start = (_start), \ -} - -#define VMSTATE_VBUFFER_UINT32(_field, _state, _version, _test, _start, _field_size) { \ - .name = (stringify(_field)), \ - .version_id = (_version), \ - .field_exists = (_test), \ - .size_offset = vmstate_offset_value(_state, _field_size, uint32_t),\ + .get_bufsize = (_get_bufsize), \ .info = &vmstate_info_buffer, \ .flags = VMS_VBUFFER|VMS_POINTER, \ .offset = offsetof(_state, _field), \ @@ -891,14 +867,11 @@ extern const VMStateDescription vmstate_hid_ptr_device; #define VMSTATE_BUFFER_START_MIDDLE(_f, _s, _start) \ VMSTATE_STATIC_BUFFER(_f, _s, 0, NULL, _start, sizeof(typeof_field(_s, _f))) -#define VMSTATE_PARTIAL_VBUFFER(_f, _s, _size) \ - VMSTATE_VBUFFER(_f, _s, 0, NULL, 0, _size) - -#define VMSTATE_PARTIAL_VBUFFER_UINT32(_f, _s, _size) \ - VMSTATE_VBUFFER_UINT32(_f, _s, 0, NULL, 0, _size) +#define VMSTATE_PARTIAL_VBUFFER(_f, _s, _get_bufsize) \ + VMSTATE_VBUFFER(_f, _s, 0, NULL, 0, _get_bufsize) -#define VMSTATE_SUB_VBUFFER(_f, _s, _start, _size) \ - VMSTATE_VBUFFER(_f, _s, 0, NULL, _start, _size) +#define VMSTATE_SUB_VBUFFER(_f, _s, _start, _get_bufsize) \ + VMSTATE_VBUFFER(_f, _s, 0, NULL, _start, _get_bufsize) #define VMSTATE_BUFFER_TEST(_f, _s, _test) \ VMSTATE_STATIC_BUFFER(_f, _s, 0, _test, 0, sizeof(typeof_field(_s, _f))) diff --git a/hw/m48t59.c b/hw/m48t59.c index c043996..4e4c9f3 100644 --- a/hw/m48t59.c +++ b/hw/m48t59.c @@ -582,6 +582,11 @@ static const MemoryRegionOps nvram_ops = { .endianness = DEVICE_NATIVE_ENDIAN, }; +static int m48t59_get_bufsize(void *opaque, int version_id) +{ + return ((M48t59State *)opaque)->size; +} + static const VMStateDescription vmstate_m48t59 = { .name = "m48t59", .version_id = 1, @@ -590,7 +595,7 @@ static const VMStateDescription vmstate_m48t59 = { .fields = (VMStateField[]) { VMSTATE_UINT8(lock, M48t59State), VMSTATE_UINT16(addr, M48t59State), - VMSTATE_VBUFFER_UINT32(buffer, M48t59State, 0, NULL, 0, size), + VMSTATE_VBUFFER(buffer, M48t59State, 0, NULL, 0, m48t59_get_bufsize), VMSTATE_END_OF_LIST() } }; diff --git a/hw/mac_nvram.c b/hw/mac_nvram.c index ed0a2b7..f4367f8 100644 --- a/hw/mac_nvram.c +++ b/hw/mac_nvram.c @@ -100,13 +100,19 @@ static const MemoryRegionOps macio_nvram_ops = { .endianness = DEVICE_NATIVE_ENDIAN, }; +static int macio_nvram_get_datasize(void *opaque, int version_id) +{ + return ((MacIONVRAMState *)opaque)->size; +} + static const VMStateDescription vmstate_macio_nvram = { .name = "macio_nvram", .version_id = 1, .minimum_version_id = 1, .minimum_version_id_old = 1, .fields = (VMStateField[]) { - VMSTATE_VBUFFER_UINT32(data, MacIONVRAMState, 0, NULL, 0, size), + VMSTATE_VBUFFER(data, MacIONVRAMState, 0, NULL, 0, + macio_nvram_get_datasize), VMSTATE_END_OF_LIST() } }; diff --git a/hw/onenand.c b/hw/onenand.c index a9d8d67..3e2016b 100644 --- a/hw/onenand.c +++ b/hw/onenand.c @@ -160,6 +160,11 @@ static int onenand_post_load(void *opaque, int version_id) return 0; } +static int onenand_get_blockwpsize(void *opaque, int version_id) +{ + return ((OneNANDState *)opaque)->blocks; +} + static const VMStateDescription vmstate_onenand = { .name = "onenand", .version_id = 1, @@ -181,7 +186,7 @@ static const VMStateDescription vmstate_onenand = { VMSTATE_UINT16(intstatus, OneNANDState), VMSTATE_UINT16(wpstatus, OneNANDState), VMSTATE_INT32(secs_cur, OneNANDState), - VMSTATE_PARTIAL_VBUFFER(blockwp, OneNANDState, blocks), + VMSTATE_PARTIAL_VBUFFER(blockwp, OneNANDState, onenand_get_blockwpsize), VMSTATE_UINT8(ecc.cp, OneNANDState), VMSTATE_UINT16_ARRAY(ecc.lp, OneNANDState, 2), VMSTATE_UINT16(ecc.count, OneNANDState), diff --git a/savevm.c b/savevm.c index f153c25..831c50a 100644 --- a/savevm.c +++ b/savevm.c @@ -1412,10 +1412,7 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd, int size = field->size; if (field->flags & VMS_VBUFFER) { - size = *(int32_t *)(opaque+field->size_offset); - if (field->flags & VMS_MULTIPLY) { - size *= field->size; - } + size = field->get_bufsize(opaque, version_id); } if (field->flags & VMS_ARRAY) { n_elems = field->num; @@ -1476,10 +1473,7 @@ void vmstate_save_state(QEMUFile *f, const VMStateDescription *vmsd, int size = field->size; if (field->flags & VMS_VBUFFER) { - size = *(int32_t *)(opaque+field->size_offset); - if (field->flags & VMS_MULTIPLY) { - size *= field->size; - } + size = field->get_bufsize(opaque, vmsd->version_id); } if (field->flags & VMS_ARRAY) { n_elems = field->num; -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 31+ messages in thread
* [Qemu-devel] [PATCH V2 2/3] hw/sd.c: add SD card save/load support 2011-12-28 12:08 ` [Qemu-devel] [PATCH V2 0/3] Improve SD controllers emulation Mitsyanko Igor 2011-12-28 12:08 ` [Qemu-devel] [PATCH V2 1/3] vmstate: introduce get_bufsize entry in VMStateField Mitsyanko Igor @ 2011-12-28 12:08 ` Mitsyanko Igor 2011-12-28 13:26 ` Peter Maydell 2011-12-28 12:08 ` [Qemu-devel] [PATCH V2 3/3] hw: Introduce spec. ver. 2.00 compliant SD host controller Mitsyanko Igor 2 siblings, 1 reply; 31+ messages in thread From: Mitsyanko Igor @ 2011-12-28 12:08 UTC (permalink / raw) To: qemu-devel Cc: peter.maydell, Mitsyanko Igor, e.voevodin, quintela, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee, afaerber We couldn't properly implement save/restore functionality of SD host controllers states without SD card's state VMStateDescription implementation. This patch updates SD card emulation to support save/load of card's state. Update requires changing of data type of several variables in SDState. Variables order rearranged to ensure proper data alignment in SDState structure. For consistency, because several variables now have bool datatype, API was modified to use bool as well, 0 was changed to 'false' and 1 was changed to 'true' in those places where it was appropriate. Signed-off-by: Mitsyanko Igor <i.mitsyanko@samsung.com> --- hw/sd.c | 130 ++++++++++++++++++++++++++++++++++++++++++--------------------- hw/sd.h | 4 +- 2 files changed, 89 insertions(+), 45 deletions(-) diff --git a/hw/sd.c b/hw/sd.c index 07eb263..4b5b538 100644 --- a/hw/sd.c +++ b/hw/sd.c @@ -54,49 +54,53 @@ typedef enum { sd_illegal = -2, } sd_rsp_type_t; +enum { + sd_inactive, + sd_card_identification_mode, + sd_data_transfer_mode, +}; + +enum { + sd_inactive_state = -1, + sd_idle_state = 0, + sd_ready_state, + sd_identification_state, + sd_standby_state, + sd_transfer_state, + sd_sendingdata_state, + sd_receivingdata_state, + sd_programming_state, + sd_disconnect_state, +}; + struct SDState { - enum { - sd_inactive, - sd_card_identification_mode, - sd_data_transfer_mode, - } mode; - enum { - sd_inactive_state = -1, - sd_idle_state = 0, - sd_ready_state, - sd_identification_state, - sd_standby_state, - sd_transfer_state, - sd_sendingdata_state, - sd_receivingdata_state, - sd_programming_state, - sd_disconnect_state, - } state; + int32_t state; uint32_t ocr; uint8_t scr[8]; uint8_t cid[16]; uint8_t csd[16]; - uint16_t rca; uint32_t card_status; uint8_t sd_status[64]; uint32_t vhs; - int wp_switch; - int *wp_groups; uint64_t size; - int blk_len; + uint32_t blk_len; uint32_t erase_start; uint32_t erase_end; uint8_t pwd[16]; - int pwd_len; - int function_group[6]; - - int spi; - int current_cmd; + uint32_t pwd_len; + uint8_t function_group[6]; + uint8_t mode; + uint8_t current_cmd; /* True if we will handle the next command as an ACMD. Note that this does * *not* track the APP_CMD status bit! */ - int expecting_acmd; - int blk_written; + bool expecting_acmd; + bool spi; + bool enable; + bool wp_switch; + bool *wp_groups; + + uint32_t blk_written; uint64_t data_start; uint32_t data_offset; uint8_t data[512]; @@ -104,8 +108,7 @@ struct SDState { qemu_irq inserted_cb; BlockDriverState *bdrv; uint8_t *buf; - - int enable; + uint16_t rca; }; static void sd_set_mode(SDState *sd) @@ -415,14 +418,14 @@ static void sd_reset(SDState *sd, BlockDriverState *bdrv) if (sd->wp_groups) g_free(sd->wp_groups); sd->wp_switch = bdrv ? bdrv_is_read_only(bdrv) : 0; - sd->wp_groups = (int *) g_malloc0(sizeof(int) * sect); - memset(sd->function_group, 0, sizeof(int) * 6); + sd->wp_groups = (bool *)g_malloc0(sizeof(bool) * sect); + memset(sd->function_group, 0, sizeof(sd->function_group)); sd->erase_start = 0; sd->erase_end = 0; sd->size = size; sd->blk_len = 0x200; sd->pwd_len = 0; - sd->expecting_acmd = 0; + sd->expecting_acmd = false; } static void sd_cardchange(void *opaque, bool load) @@ -440,23 +443,64 @@ static const BlockDevOps sd_block_ops = { .change_media_cb = sd_cardchange, }; + +static int sd_get_wpgroups_size(void *opaque, int version_id) +{ + SDState *sd = (SDState *)opaque; + return sizeof(bool) * (sd->size >> (HWBLOCK_SHIFT + SECTOR_SHIFT + + WPGROUP_SHIFT)); +} + +static const VMStateDescription sd_vmstate = { + .name = "sd_card", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_INT32(state, SDState), + VMSTATE_UINT8_ARRAY(cid, SDState, 16), + VMSTATE_UINT8_ARRAY(csd, SDState, 16), + VMSTATE_UINT32(card_status, SDState), + VMSTATE_PARTIAL_BUFFER(sd_status, SDState, 1), + VMSTATE_UINT32(vhs, SDState), + VMSTATE_UINT32(blk_len, SDState), + VMSTATE_UINT32(erase_start, SDState), + VMSTATE_UINT32(erase_end, SDState), + VMSTATE_UINT8_ARRAY(pwd, SDState, 16), + VMSTATE_UINT32(pwd_len, SDState), + VMSTATE_UINT8_ARRAY(function_group, SDState, 6), + VMSTATE_UINT8(mode, SDState), + VMSTATE_UINT8(current_cmd, SDState), + VMSTATE_BOOL(expecting_acmd, SDState), + VMSTATE_BOOL(enable, SDState), + VMSTATE_VBUFFER(wp_groups, SDState, 1, NULL, 0, sd_get_wpgroups_size), + VMSTATE_UINT32(blk_written, SDState), + VMSTATE_UINT64(data_start, SDState), + VMSTATE_UINT32(data_offset, SDState), + VMSTATE_UINT8_ARRAY(data, SDState, 512), + VMSTATE_BUFFER_UNSAFE(buf, SDState, 1, 512), + VMSTATE_UINT16(rca, SDState), + VMSTATE_END_OF_LIST() + } +}; + /* We do not model the chip select pin, so allow the board to select whether card should be in SSI or MMC/SD mode. It is also up to the board to ensure that ssi transfers only occur when the chip select is asserted. */ -SDState *sd_init(BlockDriverState *bs, int is_spi) +SDState *sd_init(BlockDriverState *bs, bool is_spi) { SDState *sd; sd = (SDState *) g_malloc0(sizeof(SDState)); sd->buf = qemu_blockalign(bs, 512); sd->spi = is_spi; - sd->enable = 1; + sd->enable = true; sd_reset(sd, bs); if (sd->bdrv) { bdrv_attach_dev_nofail(sd->bdrv, sd); bdrv_set_dev_ops(sd->bdrv, &sd_block_ops, sd); } + vmstate_register(NULL, -1, &sd_vmstate, sd); return sd; } @@ -534,7 +578,7 @@ static void sd_function_switch(SDState *sd, uint32_t arg) sd->data[66] = crc & 0xff; } -static inline int sd_wp_addr(SDState *sd, uint32_t addr) +static inline bool sd_wp_addr(SDState *sd, uint32_t addr) { return sd->wp_groups[addr >> (HWBLOCK_SHIFT + SECTOR_SHIFT + WPGROUP_SHIFT)]; @@ -560,7 +604,7 @@ static void sd_lock_command(SDState *sd) sd->card_status |= LOCK_UNLOCK_FAILED; return; } - memset(sd->wp_groups, 0, sizeof(int) * (sd->size >> + memset(sd->wp_groups, 0, sizeof(bool) * (sd->size >> (HWBLOCK_SHIFT + SECTOR_SHIFT + WPGROUP_SHIFT))); sd->csd[14] &= ~0x10; sd->card_status &= ~CARD_IS_LOCKED; @@ -1008,7 +1052,7 @@ static sd_rsp_type_t sd_normal_command(SDState *sd, sd->state = sd_programming_state; sd->wp_groups[addr >> (HWBLOCK_SHIFT + - SECTOR_SHIFT + WPGROUP_SHIFT)] = 1; + SECTOR_SHIFT + WPGROUP_SHIFT)] = true; /* Bzzzzzzztt .... Operation complete. */ sd->state = sd_transfer_state; return sd_r1b; @@ -1028,7 +1072,7 @@ static sd_rsp_type_t sd_normal_command(SDState *sd, sd->state = sd_programming_state; sd->wp_groups[addr >> (HWBLOCK_SHIFT + - SECTOR_SHIFT + WPGROUP_SHIFT)] = 0; + SECTOR_SHIFT + WPGROUP_SHIFT)] = false; /* Bzzzzzzztt .... Operation complete. */ sd->state = sd_transfer_state; return sd_r1b; @@ -1125,7 +1169,7 @@ static sd_rsp_type_t sd_normal_command(SDState *sd, if (sd->rca != rca) return sd_r0; - sd->expecting_acmd = 1; + sd->expecting_acmd = true; sd->card_status |= APP_CMD; return sd_r1; @@ -1307,7 +1351,7 @@ int sd_do_command(SDState *sd, SDRequest *req, if (sd->card_status & CARD_IS_LOCKED) { if (!cmd_valid_while_locked(sd, req)) { sd->card_status |= ILLEGAL_COMMAND; - sd->expecting_acmd = 0; + sd->expecting_acmd = false; fprintf(stderr, "SD: Card is locked\n"); rtype = sd_illegal; goto send_response; @@ -1318,7 +1362,7 @@ int sd_do_command(SDState *sd, SDRequest *req, sd_set_mode(sd); if (sd->expecting_acmd) { - sd->expecting_acmd = 0; + sd->expecting_acmd = false; rtype = sd_app_command(sd, *req); } else { rtype = sd_normal_command(sd, *req); @@ -1704,7 +1748,7 @@ int sd_data_ready(SDState *sd) return sd->state == sd_sendingdata_state; } -void sd_enable(SDState *sd, int enable) +void sd_enable(SDState *sd, bool enable) { sd->enable = enable; } diff --git a/hw/sd.h b/hw/sd.h index ac4b7c4..d25342f 100644 --- a/hw/sd.h +++ b/hw/sd.h @@ -67,13 +67,13 @@ typedef struct { typedef struct SDState SDState; -SDState *sd_init(BlockDriverState *bs, int is_spi); +SDState *sd_init(BlockDriverState *bs, bool is_spi); int sd_do_command(SDState *sd, SDRequest *req, uint8_t *response); void sd_write_data(SDState *sd, uint8_t value); uint8_t sd_read_data(SDState *sd); void sd_set_cb(SDState *sd, qemu_irq readonly, qemu_irq insert); int sd_data_ready(SDState *sd); -void sd_enable(SDState *sd, int enable); +void sd_enable(SDState *sd, bool enable); #endif /* __hw_sd_h */ -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 31+ messages in thread
* Re: [Qemu-devel] [PATCH V2 2/3] hw/sd.c: add SD card save/load support 2011-12-28 12:08 ` [Qemu-devel] [PATCH V2 2/3] hw/sd.c: add SD card save/load support Mitsyanko Igor @ 2011-12-28 13:26 ` Peter Maydell 2011-12-28 14:02 ` Mitsyanko Igor 0 siblings, 1 reply; 31+ messages in thread From: Peter Maydell @ 2011-12-28 13:26 UTC (permalink / raw) To: Mitsyanko Igor Cc: e.voevodin, quintela, qemu-devel, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee, afaerber On 28 December 2011 12:08, Mitsyanko Igor <i.mitsyanko@samsung.com> wrote: > We couldn't properly implement save/restore functionality of SD host controllers > states without SD card's state VMStateDescription implementation. This patch > updates SD card emulation to support save/load of card's state. Update requires > changing of data type of several variables in SDState. Variables order rearranged > to ensure proper data alignment in SDState structure. > For consistency, because several variables now have bool datatype, API was modified > to use bool as well, 0 was changed to 'false' and 1 was changed to 'true' in those > places where it was appropriate. If you're going to switch things to bool, can you break those out into separate patches for the individual things you're changing, please? Otherwise this patch is trying to do too many things at once. Also, why should we care particularly about the order of fields in SDState? There will be at most a handful of copies of this struct in qemu, costing a handful of bytes in extra padding. ("ensure proper data alignment" is wrong -- the compiler does this for us.) If you feel you must rearrange things, again, please put it in a separate patch so it's easier to read. > @@ -415,14 +418,14 @@ static void sd_reset(SDState *sd, BlockDriverState *bdrv) > if (sd->wp_groups) > g_free(sd->wp_groups); > sd->wp_switch = bdrv ? bdrv_is_read_only(bdrv) : 0; > - sd->wp_groups = (int *) g_malloc0(sizeof(int) * sect); > - memset(sd->function_group, 0, sizeof(int) * 6); > + sd->wp_groups = (bool *)g_malloc0(sizeof(bool) * sect); sd->wp_groups = g_new0(bool, sect); -- PMM ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [Qemu-devel] [PATCH V2 2/3] hw/sd.c: add SD card save/load support 2011-12-28 13:26 ` Peter Maydell @ 2011-12-28 14:02 ` Mitsyanko Igor 2011-12-28 14:41 ` Peter Maydell 0 siblings, 1 reply; 31+ messages in thread From: Mitsyanko Igor @ 2011-12-28 14:02 UTC (permalink / raw) To: qemu-devel Cc: Peter Maydell, e.voevodin, quintela, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee, afaerber On 12/28/2011 05:26 PM, Peter Maydell wrote: > On 28 December 2011 12:08, Mitsyanko Igor<i.mitsyanko@samsung.com> wrote: >> We couldn't properly implement save/restore functionality of SD host controllers >> states without SD card's state VMStateDescription implementation. This patch >> updates SD card emulation to support save/load of card's state. Update requires >> changing of data type of several variables in SDState. Variables order rearranged >> to ensure proper data alignment in SDState structure. >> For consistency, because several variables now have bool datatype, API was modified >> to use bool as well, 0 was changed to 'false' and 1 was changed to 'true' in those >> places where it was appropriate. > > If you're going to switch things to bool, can you break those out > into separate patches for the individual things you're changing, > please? Otherwise this patch is trying to do too many things > at once. Sure, I'll split this patch into a few smaller ones, thanks. > Also, why should we care particularly about the order of > fields in SDState? There will be at most a handful of copies > of this struct in qemu, costing a handful of bytes in extra > padding. ("ensure proper data alignment" is wrong -- the compiler > does this for us.) If you feel you must rearrange things, again, > please put it in a separate patch so it's easier to read. > Why not, it wouldn't hurt anyone. -- Mitsyanko Igor ASWG, Moscow R&D center, Samsung Electronics email: i.mitsyanko@samsung.com ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [Qemu-devel] [PATCH V2 2/3] hw/sd.c: add SD card save/load support 2011-12-28 14:02 ` Mitsyanko Igor @ 2011-12-28 14:41 ` Peter Maydell 0 siblings, 0 replies; 31+ messages in thread From: Peter Maydell @ 2011-12-28 14:41 UTC (permalink / raw) To: i.mitsyanko Cc: e.voevodin, quintela, qemu-devel, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee, afaerber On 28 December 2011 14:02, Mitsyanko Igor <i.mitsyanko@samsung.com> wrote: > On 12/28/2011 05:26 PM, Peter Maydell wrote: >> Also, why should we care particularly about the order of >> fields in SDState? There will be at most a handful of copies >> of this struct in qemu, costing a handful of bytes in extra >> padding. ("ensure proper data alignment" is wrong -- the compiler >> does this for us.) If you feel you must rearrange things, again, >> please put it in a separate patch so it's easier to read. >> > Why not, it wouldn't hurt anyone. Basically I think this is pointless microoptimisation. If you haven't measured something and determined that this is a useful change because it makes things faster or usefully decreases memory use by some non-trivial amount, then this kind of change is a poor use of your time and also mine (as reviewer) and of whoever eventually commits the patch to git. -- PMM ^ permalink raw reply [flat|nested] 31+ messages in thread
* [Qemu-devel] [PATCH V2 3/3] hw: Introduce spec. ver. 2.00 compliant SD host controller 2011-12-28 12:08 ` [Qemu-devel] [PATCH V2 0/3] Improve SD controllers emulation Mitsyanko Igor 2011-12-28 12:08 ` [Qemu-devel] [PATCH V2 1/3] vmstate: introduce get_bufsize entry in VMStateField Mitsyanko Igor 2011-12-28 12:08 ` [Qemu-devel] [PATCH V2 2/3] hw/sd.c: add SD card save/load support Mitsyanko Igor @ 2011-12-28 12:08 ` Mitsyanko Igor 2 siblings, 0 replies; 31+ messages in thread From: Mitsyanko Igor @ 2011-12-28 12:08 UTC (permalink / raw) To: qemu-devel Cc: peter.maydell, Mitsyanko Igor, e.voevodin, quintela, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee, afaerber This patch adds implementation of "SD specification version 2.00" compliant SD host controller. Also it provides interface to implement SoC-specific controllers on top of this specification-compliant one. Signed-off-by: Mitsyanko Igor <i.mitsyanko@samsung.com> --- Makefile.target | 1 + hw/sdhc_ver2.c | 1569 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/sdhc_ver2.h | 327 ++++++++++++ 3 files changed, 1897 insertions(+), 0 deletions(-) create mode 100644 hw/sdhc_ver2.c create mode 100644 hw/sdhc_ver2.h diff --git a/Makefile.target b/Makefile.target index 3261383..79c33ac 100644 --- a/Makefile.target +++ b/Makefile.target @@ -358,6 +358,7 @@ obj-arm-y += vexpress.o obj-arm-y += strongarm.o obj-arm-y += collie.o obj-arm-y += pl041.o lm4549.o +obj-arm-y += sdhc_ver2.o obj-sh4-y = shix.o r2d.o sh7750.o sh7750_regnames.o tc58128.o obj-sh4-y += sh_timer.o sh_serial.o sh_intc.o sh_pci.o sm501.o diff --git a/hw/sdhc_ver2.c b/hw/sdhc_ver2.c new file mode 100644 index 0000000..1803ae6 --- /dev/null +++ b/hw/sdhc_ver2.c @@ -0,0 +1,1569 @@ +/* + * SD host controller (SD Host Controller Simplified + * Specification Version 2.00 compliant) emulation + * + * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. + * All rights reserved. + * Contributed by Mitsyanko Igor <i.mitsyanko@samsung.com> + * + * Based on MMC controller for Samsung S5PC1xx-based board emulation + * by Alexey Merkulov and Vladimir Monakhov. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "hw.h" +#include "block_int.h" +#include "blockdev.h" +#include "qemu-timer.h" +#include "sdhc_ver2.h" + +/* host controller debug messages */ +#define SDHC_DEBUG 0 + +#if SDHC_DEBUG == 0 + #define DPRINT_L1(fmt, args...) do { } while (0) + #define DPRINT_L2(fmt, args...) do { } while (0) + #define DPRINT_ERROR(fmt, args...) do { } while (0) +#elif SDHC_DEBUG == 1 + #define DPRINT_L1(fmt, args...) \ + do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0) + #define DPRINT_L2(fmt, args...) do { } while (0) + #define DPRINT_ERROR(fmt, args...) \ + do {fprintf(stderr, "QEMU SDHC ERROR: "fmt, ## args); } while (0) +#else + #define DPRINT_L1(fmt, args...) \ + do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0) + #define DPRINT_L2(fmt, args...) \ + do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0) + #define DPRINT_ERROR(fmt, args...) \ + do {fprintf(stderr, "QEMU SDHC ERROR: "fmt, ## args); } while (0) +#endif + +/* Default SD/MMC host controller features information, which will be + * presented in CAPABILITIES register of generic SD host controller at reset. + * If not stated otherwise: + * 0 - not supported, 1 - supported, other - prohibited. + */ +#define SDHC_CAPAB_64BITBUS 0ul /* 64-bit System Bus Support */ +#define SDHC_CAPAB_18V 1ul /* Voltage support 1.8v */ +#define SDHC_CAPAB_30V 0ul /* Voltage support 3.0v */ +#define SDHC_CAPAB_33V 1ul /* Voltage support 3.3v */ +#define SDHC_CAPAB_SUSPRESUME 0ul /* Suspend/resume support */ +#define SDHC_CAPAB_SDMA 1ul /* SDMA support */ +#define SDHC_CAPAB_HIGHSPEED 1ul /* High speed support */ +#define SDHC_CAPAB_ADMA 1ul /* ADMA2 support */ +/* Maximum host controller R/W buffers size + * Possible values: 512, 1024, 2048 bytes */ +#define SDHC_CAPAB_MAXBLOCKLENGTH 512ul +/* Maximum clock frequency for SDclock in MHz + * value in range 10-63 MHz, 0 - not defined */ +#define SDHC_CAPAB_BASECLKFREQ 0ul +#define SDHC_CAPAB_TOUNIT 1ul /* Timeout clock unit 0 - kHz, 1 - MHz */ +/* Timeout clock frequency 1-63, 0 - not defined */ +#define SDHC_CAPAB_TOCLKFREQ 0ul + +/* Now check all parameters and calculate CAPABILITIES REGISTER value */ +#if SDHC_CAPAB_64BITBUS > 1 || SDHC_CAPAB_18V > 1 || SDHC_CAPAB_30V > 1 ||\ + SDHC_CAPAB_33V > 1 || SDHC_CAPAB_SUSPRESUME > 1 || SDHC_CAPAB_SDMA > 1 ||\ + SDHC_CAPAB_HIGHSPEED > 1 || SDHC_CAPAB_ADMA > 1 || SDHC_CAPAB_TOUNIT > 1 +#error Capabilities features SDHC_CAPAB_x must have value 0 or 1! +#endif + +#if SDHC_CAPAB_MAXBLOCKLENGTH == 512 +#define MAX_BLOCK_LENGTH 0ul +#elif SDHC_CAPAB_MAXBLOCKLENGTH == 1024 +#define MAX_BLOCK_LENGTH 1ul +#elif SDHC_CAPAB_MAXBLOCKLENGTH == 2048 +#define MAX_BLOCK_LENGTH 2ul +#else +#error Max host controller block size can have value 512, 1024 or 2048 only! +#endif + +#if (SDHC_CAPAB_BASECLKFREQ > 0 && SDHC_CAPAB_BASECLKFREQ < 10) || \ + SDHC_CAPAB_BASECLKFREQ > 63 +#error SDclock frequency can have value in range 0, 10-63 only! +#endif + +#if SDHC_CAPAB_TOCLKFREQ > 63 +#error Timeout clock frequency can have value in range 0-63 only! +#endif + +#define SDHC_CAPAB_REG_DEFAULT \ + ((SDHC_CAPAB_64BITBUS<<28)|(SDHC_CAPAB_18V<<26)|\ + (SDHC_CAPAB_30V<<25)|(SDHC_CAPAB_33V<<24)|(SDHC_CAPAB_SUSPRESUME<<23)|\ + (SDHC_CAPAB_SDMA<<22)|(SDHC_CAPAB_HIGHSPEED<<21)|(SDHC_CAPAB_ADMA<<19)|\ + (MAX_BLOCK_LENGTH<<16)|(SDHC_CAPAB_BASECLKFREQ<<8)|(SDHC_CAPAB_TOUNIT<<7)|\ + (SDHC_CAPAB_TOCLKFREQ)) + +static void sdhcv2_transfer_complete_irq(void *opaque) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + + /* free data transfer line */ + s->prnsts &= ~(SDHC_DOING_READ | SDHC_DOING_WRITE | + SDHC_DAT_LINE_ACTIVE | SDHC_DATA_INHIBIT | + SDHC_SPACE_AVAILABLE | SDHC_DATA_AVAILABLE); + + if (s->norintstsen & SDHC_NISEN_TRSCMP) { + s->norintsts |= SDHC_NIS_TRSCMP; + } + s->slotint |= (s->norintsigen & s->norintsts) ? 1 : 0; + qemu_set_irq(s->irq, s->norintsigen & s->norintsts); +} + +/* raise command response received interrupt */ +void sdhcv2_raise_response_recieved_irq(SDHCv2State *s) +{ + DPRINT_L2("raise IRQ response\n"); + + if (s->norintstsen & SDHC_NISEN_CMDCMP) { + s->norintsts |= SDHC_NIS_CMDCMP; + } + + DPRINT_L2("Interrupt request %s\n", (s->norintsts & s->norintsigen) || + (s->errintsts & s->errintsigen) ? "raised" : "lowered"); + s->slotint |= ((s->norintsts & s->norintsigen) || + (s->errintsts & s->errintsigen)) ? 1 : 0; + qemu_set_irq(s->irq, (s->norintsts & s->norintsigen) || + (s->errintsts & s->errintsigen)); +} + +static void sdhcv2_raise_insertion_irq(void *opaque) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + bool int_raised = false; + + if (s->norintsts & SDHC_NIS_REMOVE) { + qemu_mod_timer(s->insert_timer, + qemu_get_clock_ns(vm_clock) + SDHC_INSERTION_DELAY); + } else { + s->prnsts = 0x1ff0000; + if (s->norintstsen & SDHC_NISEN_INSERT) { + s->norintsts |= SDHC_NIS_INSERT; + int_raised = (s->norintsigen & SDHC_NORINTSIG_INSERT) || + (s->wakcon & SDHC_WKUP_ON_INSERT); + s->slotint |= (int_raised ? 1 : 0); + } + qemu_set_irq(s->irq, int_raised); + } +} + +static void sdhcv2_insert_eject_cb(void *opaque, int irq, int level) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + DPRINT_L1("Card state changed: %s!\n", level ? "insert" : "eject"); + bool int_raised = false; + + if (s->norintsts & SDHC_NIS_REMOVE) { + if (level) { + DPRINT_L2("Change card state: timer set!\n"); + qemu_mod_timer(s->insert_timer, + qemu_get_clock_ns(vm_clock) + SDHC_INSERTION_DELAY); + } + } else { + if (level) { + s->prnsts = 0x1ff0000; + if (s->norintstsen & SDHC_NISEN_INSERT) { + s->norintsts |= SDHC_NIS_INSERT; + int_raised = (s->norintsigen & SDHC_NORINTSIG_INSERT) || + (s->wakcon & SDHC_WKUP_ON_INSERT); + s->slotint |= (int_raised ? 1 : 0); + } + } else { + s->prnsts = 0x1fa0000; + s->pwrcon &= ~SDHC_POWER_ON; + s->clkcon &= ~SDHC_CLOCK_SDCLK_EN; + if (s->norintstsen & SDHC_NISEN_REMOVE) { + s->norintsts |= SDHC_NIS_REMOVE; + int_raised = (s->norintsigen & SDHC_NORINTSIG_REMOVE) || + (s->wakcon & SDHC_WKUP_ON_REMOVE); + s->slotint |= (int_raised ? 1 : 0); + } + } + qemu_set_irq(s->irq, int_raised); + } +} + +static void sdhcv2_card_readonly_cb(void *opaque, int irq, int level) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + level ? (s->prnsts &= ~SDHC_WRITE_PROTECT) : + (s->prnsts |= SDHC_WRITE_PROTECT); +} + +void sdhcv2_reset(SDHCv2State *s) +{ + /* Set all registers to 0. Capabilities registers are not cleared + * and assumed to always preserve their value, given to them during + * initialization */ + memset(&s->sdmasysad, 0, (uintptr_t)&s->capareg - (uintptr_t)&s->sdmasysad); + + s->card ? (s->prnsts = 0x1ff0000) : (s->prnsts = 0x1fa0000); + s->data_count = 0; + s->stoped_state = sdhc_not_stoped; +} + +void sdhcv2_send_command(SDHCv2State *s) +{ + SDRequest request; + uint8_t response[16]; + int rlen; + s->errintsts = 0; + s->acmd12errsts = 0; + if (!s->card) { + goto error; + } + + request.cmd = s->cmdreg >> 8; + request.arg = s->argument; + DPRINT_L1("Sending command %u with argument %08x\n", + request.cmd, request.arg); + rlen = sd_do_command(s->card, &request, response); + if (rlen < 0) { + goto error; + } + if ((s->cmdreg & SDHC_CMD_RESPONSE) != 0) { +#define RWORD(n) ((n >= 0 ? (response[n] << 24) : 0) \ + | (response[n + 1] << 16) \ + | (response[n + 2] << 8) \ + | response[n + 3]) + + if ((rlen == 0) || (rlen != 4 && rlen != 16)) { + goto error; + } + + s->rspreg[0] = RWORD(0); + if (rlen == 4) { + s->rspreg[1] = s->rspreg[2] = s->rspreg[3] = 0; + } else { + s->rspreg[0] = RWORD(11); + s->rspreg[1] = RWORD(7); + s->rspreg[2] = RWORD(3); + s->rspreg[3] = RWORD(-1); + } +#undef RWORD + DPRINT_L1("Response received:\n RSPREG[127..96]=0x%08x, RSPREG[95..64]=" + "0x%08x,\n RSPREG[63..32]=0x%08x, RSPREG[31..0]=0x%08x\n", + s->rspreg[3], s->rspreg[2], s->rspreg[1], s->rspreg[0]); + } + return; + +error: + DPRINT_ERROR("Timeout waiting for command response\n"); + if (s->errintstsen & SDHC_EISEN_CMDTIMEOUT) { + s->errintsts |= SDHC_EIS_CMDTIMEOUT; + s->norintsts |= SDHC_NIS_ERR; + } +} + +void sdhcv2_do_transfer_complete(SDHCv2State *s) +{ + /* Automatically send CMD12 to stop transfer if AutoCMD12 enabled */ + if ((s->trnmod & SDHC_TRNS_ACMD12) != 0) { + SDRequest request; + uint8_t response[16]; + + request.cmd = 0x0C; + request.arg = 0; + DPRINT_L1("Automatically issue CMD%d %08x\n", request.cmd, request.arg); + sd_do_command(s->card, &request, response); + /* Auto CMD12 response goes to the upper Response register */ + s->rspreg[3] = (response[0] << 24) | (response[1] << 16) | + (response[2] << 8) | response[3]; + } + /* pend a timer which will raise a transfer complete irq */ + qemu_mod_timer(s->transfer_complete_timer, + qemu_get_clock_ns(vm_clock) + 1); +} + +/* + * Programmed i/o data transfer + */ + +/* Fill host controller's read buffer with BLKSIZE bytes of data from card */ +static void sdhcv2_read_block_from_card(void *opaque) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + int index = 0; + + if ((s->trnmod & SDHC_TRNS_MULTI) && + (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0)) { + return; + } + + for (index = 0; index < (s->blksize & 0x0fff); index++) { + s->fifo_buffer[index] = sd_read_data(s->card); + } + + /* New data now available for READ through Buffer Port Register */ + s->prnsts |= SDHC_DATA_AVAILABLE; + if (s->norintstsen & SDHC_NISEN_RBUFRDY) { + s->norintsts |= SDHC_NIS_RBUFRDY; + } + + /* Clear DAT line active status if that was the last block */ + if ((s->trnmod & SDHC_TRNS_MULTI) == 0 || + ((s->trnmod & SDHC_TRNS_MULTI) && s->blkcnt == 1)) { + s->prnsts &= ~SDHC_DAT_LINE_ACTIVE; + } + + /* If stop at block gap request was set and it's not the last block of + * data - generate Block Event interrupt */ + if (s->stoped_state == sdhc_gap_read && (s->trnmod & SDHC_TRNS_MULTI) && + s->blkcnt != 1) { + s->prnsts &= ~SDHC_DAT_LINE_ACTIVE; + if (s->norintstsen & SDHC_EISEN_BLKGAP) { + s->norintsts |= SDHC_EIS_BLKGAP; + } + } + + s->slotint |= (s->norintsts & s->norintsigen) ? 1 : 0; + qemu_set_irq(s->irq, s->norintsts & s->norintsigen); +} + +/* Read @size byte of data from host controller @s BUFFER DATA PORT register */ +uint32_t sdhcv2_read_dataport(SDHCv2State *s, unsigned size) +{ + uint32_t value = 0; + int i; + + /* first check that a valid data exists in host controller input buffer */ + if ((s->prnsts & SDHC_DATA_AVAILABLE) == 0) { + DPRINT_ERROR("Trying to read from empty buffer\n"); + return 0; + } + + for (i = 0; i < size; i++) { + value |= s->fifo_buffer[s->data_count] << i * 8; + s->data_count++; + /* check if we've read all valid data (blksize bytes) from buffer */ + if ((s->data_count) >= (s->blksize & 0x0fff)) { + DPRINT_L2("All %u bytes of data have been read from input buffer\n", + s->data_count); + s->prnsts &= ~SDHC_DATA_AVAILABLE; /* no more data in a buffer */ + s->data_count = 0; /* next buff read must start at position [0] */ + + if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) { + s->blkcnt--; + } + + /* if that was the last block of data */ + if ((s->trnmod & SDHC_TRNS_MULTI) == 0 || + ((s->trnmod & SDHC_TRNS_MULTI) && + (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0))) { + sdhcv2_do_transfer_complete(s); + } else if (s->stoped_state == sdhc_gap_read && + !(s->prnsts & SDHC_DAT_LINE_ACTIVE)) { + /* stop at gap request */ + sdhcv2_transfer_complete_irq(s); + } else { /* if there are more data, read next block from card */ + qemu_mod_timer(s->read_buffer_timer, + qemu_get_clock_ns(vm_clock) + SDHC_READ_BUFFER_DELAY); + } + break; + } + } + + return value; +} + +/* Write data from host controller FIFO to card */ +static void sdhcv2_write_block_to_card(void *opaque) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + int index = 0; + + if (s->prnsts & SDHC_SPACE_AVAILABLE) { + if (s->norintstsen & SDHC_NISEN_WBUFRDY) { + s->norintsts |= SDHC_NIS_WBUFRDY; + } + s->slotint |= (s->norintsts & s->norintsigen) ? 1 : 0; + qemu_set_irq(s->irq, s->norintsigen & s->norintsts); + return; + } + + if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) { + if (s->blkcnt == 0) { + return; + } else { + s->blkcnt--; + } + } + + for (index = 0; index < (s->blksize & 0x0fff); index++) { + sd_write_data(s->card, s->fifo_buffer[index]); + } + + /* Next data can be written through BUFFER DATORT register */ + s->prnsts |= SDHC_SPACE_AVAILABLE; + if (s->norintstsen & SDHC_NISEN_WBUFRDY) { + s->norintsts |= SDHC_NIS_WBUFRDY; + } + + /* Finish transfer if that was the last block of data */ + if ((s->trnmod & SDHC_TRNS_MULTI) == 0 || + ((s->trnmod & SDHC_TRNS_MULTI) && + (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0))) { + sdhcv2_do_transfer_complete(s); + } + + /* Generate Block Gap Event if requested and if not the last block */ + if (s->stoped_state == sdhc_gap_write && (s->trnmod & SDHC_TRNS_MULTI) && + s->blkcnt > 0) { + s->prnsts &= ~SDHC_DOING_WRITE; + if (s->norintstsen & SDHC_EISEN_BLKGAP) { + s->norintsts |= SDHC_EIS_BLKGAP; + } + qemu_mod_timer(s->transfer_complete_timer, + qemu_get_clock_ns(vm_clock) + 1); + } + + s->slotint |= (s->norintsts & s->norintsigen) ? 1 : 0; + qemu_set_irq(s->irq, s->norintsigen & s->norintsts); +} + +/* Write @size bytes of @value data to host controller @s Buffer Data Port + * register */ +void sdhcv2_write_dataport(SDHCv2State *s, uint32_t value, unsigned size) +{ + unsigned i; + + /* Check that there is free space left in a buffer */ + if (!(s->prnsts & SDHC_SPACE_AVAILABLE)) { + DPRINT_ERROR("Can't write to data buffer: buffer full\n"); + return; + } + + for (i = 0; i < size; i++) { + s->fifo_buffer[s->data_count] = value & 0xFF; + s->data_count++; + value >>= 8; + if (s->data_count >= (s->blksize & 0x0fff)) { + DPRINT_L2("write buffer filled with %u bytes of data\n", + s->data_count); + s->data_count = 0; + s->prnsts &= ~SDHC_SPACE_AVAILABLE; + if (s->prnsts & SDHC_DOING_WRITE) { + qemu_mod_timer(s->write_buffer_timer, + qemu_get_clock_ns(vm_clock) + SDHC_WRITE_BUFFER_DELAY); + } + } + } +} + +/* + * Single DMA data transfer + */ + +/* Multi block SDMA transfer */ +void sdhcv2_sdma_transfer_multi_blocks(SDHCv2State *s) +{ + bool page_aligned = false; + unsigned int n, begin; + const uint16_t block_size = s->blksize & 0x0fff; + uint32_t boundary_chk = 1 << (((s->blksize & 0xf000) >> 12) + 12); + uint32_t boundary_count = boundary_chk - (s->sdmasysad % boundary_chk); + + /* XXX: Some sd/mmc drivers (for example, u-boot-slp) do not account for + * possible stop at page boundary if initial address is not page aligned, + * allow them to work properly */ + if ((s->sdmasysad % boundary_chk) == 0) { + page_aligned = true; + } + + if (s->trnmod & SDHC_TRNS_READ) { + s->prnsts |= SDHC_DOING_READ | SDHC_DATA_INHIBIT | + SDHC_DAT_LINE_ACTIVE; + while (s->blkcnt) { + if (s->data_count == 0) { + for (n = 0; n < block_size; n++) { + s->fifo_buffer[n] = sd_read_data(s->card); + } + } + begin = s->data_count; + if (((boundary_count + begin) < block_size) && page_aligned) { + s->data_count = boundary_count + begin; + boundary_count = 0; + } else { + s->data_count = block_size; + boundary_count -= block_size - begin; + s->blkcnt--; + } + cpu_physical_memory_write(s->sdmasysad, &s->fifo_buffer[begin], + s->data_count - begin); + s->sdmasysad += s->data_count - begin; + if (s->data_count == block_size) { + s->data_count = 0; + } + if (page_aligned && boundary_count == 0) { + break; + } + } + } else { + s->prnsts |= SDHC_DOING_WRITE | SDHC_DATA_INHIBIT | + SDHC_DAT_LINE_ACTIVE; + while (s->blkcnt) { + begin = s->data_count; + if (((boundary_count + begin) < block_size) && page_aligned) { + s->data_count = boundary_count + begin; + boundary_count = 0; + } else { + s->data_count = block_size; + boundary_count -= block_size - begin; + } + cpu_physical_memory_read(s->sdmasysad, + &s->fifo_buffer[begin], s->data_count); + s->sdmasysad += s->data_count - begin; + if (s->data_count == block_size) { + for (n = 0; n < block_size; n++) { + sd_write_data(s->card, s->fifo_buffer[n]); + } + s->data_count = 0; + s->blkcnt--; + } + if (page_aligned && boundary_count == 0) { + break; + } + } + } + + if (s->blkcnt == 0) { + sdhcv2_do_transfer_complete(s); + } else { + if (s->norintstsen & SDHC_NISEN_DMA) { + s->norintsts |= SDHC_NIS_DMA; + } + s->slotint |= (s->norintsts & s->norintsigen) ? 1 : 0; + qemu_set_irq(s->irq, s->norintsigen & s->norintsts); + } +} + +/* single block SDMA transfer */ +void sdhcv2_sdma_transfer_single_block(SDHCv2State *s) +{ + int n; + uint32_t datacnt = s->blksize & 0x0fff; + + if (s->trnmod & SDHC_TRNS_READ) { + for (n = 0; n < datacnt; n++) { + s->fifo_buffer[n] = sd_read_data(s->card); + } + cpu_physical_memory_write(s->sdmasysad, s->fifo_buffer, datacnt); + } else { + cpu_physical_memory_read(s->sdmasysad, s->fifo_buffer, datacnt); + for (n = 0; n < datacnt; n++) { + sd_write_data(s->card, s->fifo_buffer[n]); + } + } + + if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) { + s->blkcnt--; + } + + sdhcv2_do_transfer_complete(s); +} + +/* Advanced DMA data transfer */ +void sdhcv2_start_adma(SDHCv2State *s) +{ + unsigned int n, length, begin; + uint8_t attributes; + target_phys_addr_t entry_addr, address; + const bool is_32bit_adma = SDHC_DMA_TYPE(s->hostctl) == SDHC_CTRL_ADMA_32; + const uint16_t block_size = s->blksize & 0x0fff; + s->admaerr &= ~SDHC_ADMAERR_LENGTH_MISMATCH; + + while (1) { + address = length = attributes = 0; + + entry_addr = is_32bit_adma ? + (s->admasysaddr & 0xFFFFFFFFull) : s->admasysaddr; + /* fetch next entry from descriptor table */ + cpu_physical_memory_read(entry_addr + 4, (uint8_t *)(&address), + is_32bit_adma ? 4 : 8); + cpu_physical_memory_read(entry_addr + 2, (uint8_t *)(&length), 2); + cpu_physical_memory_read(entry_addr, (uint8_t *)(&attributes), 1); + DPRINT_L2("ADMA loop: addr=0x%x, len=%d, attr=%x\n", address, + length_table, attributes); + + if ((attributes & SDHC_ADMA_ATTR_VALID) == 0) { + /* Indicate that error occurred in ST_FDS state */ + s->admaerr &= ~SDHC_ADMAERR_STATE_MASK; + s->admaerr |= SDHC_ADMAERR_STATE_ST_FDS; + + /* Generate ADMA error interrupt */ + if (s->errintstsen & SDHC_EISEN_ADMAERR) { + s->errintsts |= SDHC_EIS_ADMAERR; + s->norintsts |= SDHC_NIS_ERR; + } + s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0; + qemu_set_irq(s->irq, s->errintsigen & s->errintsts); + break; + } + + if (length == 0) { + length = 65536; + } + /* Address must be aligned */ + address &= (is_32bit_adma ? 0xfffffffc : 0xfffffff8); + + switch (attributes & SDHC_ADMA_ATTR_ACT_MASK) { + case SDHC_ADMA_ATTR_ACT_TRAN: /* data transfer */ + if (s->trnmod & SDHC_TRNS_READ) { + while (length) { + if (s->data_count == 0) { + for (n = 0; n < block_size; n++) { + s->fifo_buffer[n] = sd_read_data(s->card); + } + } + begin = s->data_count; + if ((length + begin) < block_size) { + s->data_count = length + begin; + length = 0; + } else { + s->data_count = block_size; + length -= block_size - begin; + } + cpu_physical_memory_write(address, &s->fifo_buffer[begin], + s->data_count - begin); + address += s->data_count - begin; + if (s->data_count == block_size) { + s->data_count = 0; + if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) { + s->blkcnt--; + if (s->blkcnt == 0) { + break; + } + } + } + } + } else { + while (length) { + begin = s->data_count; + if ((length + begin) < block_size) { + s->data_count = length + begin; + length = 0; + } else { + s->data_count = block_size; + length -= block_size - begin; + } + cpu_physical_memory_read(address, + &s->fifo_buffer[begin], s->data_count); + address += s->data_count - begin; + if (s->data_count == block_size) { + for (n = 0; n < block_size; n++) { + sd_write_data(s->card, s->fifo_buffer[n]); + } + s->data_count = 0; + if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) { + s->blkcnt--; + if (s->blkcnt == 0) { + break; + } + } + } + } + } + s->admasysaddr += (is_32bit_adma ? 8 : 12); + break; + case SDHC_ADMA_ATTR_ACT_LINK: /* link to next descriptor table */ + s->admasysaddr = address; + DPRINT_L1("ADMA link: admasysaddr=0x%lx\n", s->admasysaddr); + break; + default: + s->admasysaddr += (is_32bit_adma ? 8 : 12); + break; + } + + /* ADMA transfer terminates if blkcnt == 0 or by END attribute */ + if (((s->trnmod & SDHC_TRNS_BLK_CNT_EN) && + (s->blkcnt == 0)) || (attributes & SDHC_ADMA_ATTR_END)) { + DPRINT_L2("ADMA transfer completed\n"); + if (length || ((attributes & SDHC_ADMA_ATTR_END) && + (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && + s->blkcnt != 0) || ((s->trnmod & SDHC_TRNS_BLK_CNT_EN) && + s->blkcnt == 0 && (attributes & SDHC_ADMA_ATTR_END) == 0)) { + DPRINT_ERROR("SD/MMC host ADMA length mismatch\n"); + s->admaerr |= SDHC_ADMAERR_LENGTH_MISMATCH | + SDHC_ADMAERR_STATE_ST_TFR; + if (s->errintstsen & SDHC_EISEN_ADMAERR) { + DPRINT_ERROR("Set ADMA error flag\n"); + s->errintsts |= SDHC_EIS_ADMAERR; + s->norintsts |= SDHC_NIS_ERR; + } + s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0; + qemu_set_irq(s->irq, s->errintsigen & s->errintsts); + } + sdhcv2_do_transfer_complete(s); + break; + } + + if (attributes & SDHC_ADMA_ATTR_INT) { + DPRINT_L1("ADMA interrupt: admasysaddr=0x%lx\n", s->admasysaddr); + if (s->norintstsen & SDHC_NISEN_DMA) { + s->norintsts |= SDHC_NIS_DMA; + } + s->slotint |= (s->norintsts & s->norintsigen) ? 1 : 0; + qemu_set_irq(s->irq, s->norintsigen & s->norintsts); + break; + } + } +} + +/* Perform data transfer according to controller configuration */ +void sdhcv2_transfer_data(SDHCv2State *s) +{ + if (s->trnmod & SDHC_TRNS_DMA) { + switch (SDHC_DMA_TYPE(s->hostctl)) { + case SDHC_CTRL_SDMA: + if ((s->trnmod & SDHC_TRNS_MULTI) && + (!(s->trnmod & SDHC_TRNS_BLK_CNT_EN) || s->blkcnt == 0)) { + break; + } + + if ((s->blkcnt == 1) || !(s->trnmod & SDHC_TRNS_MULTI)) { + sdhcv2_sdma_transfer_single_block(s); + } else { + s->data_count = 0; + sdhcv2_sdma_transfer_multi_blocks(s); + } + break; + case SDHC_CTRL_ADMA_32: + if (!(s->capareg & SDHC_CAN_DO_ADMA)) { + DPRINT_ERROR("ADMA32 transfer aborted: ADMA not supported as" + "states by Capabilities register\n"); + break; + } + s->data_count = 0; + sdhcv2_start_adma(s); + break; + case SDHC_CTRL_ADMA_64: + if (!(s->capareg & SDHC_CAN_DO_ADMA) || + !(s->capareg & SDHC_64_BIT_BUS_SUPPORT)) { + DPRINT_ERROR("ADMA64 transfer aborted: 64 bit ADMA not " + "supported as states by Capabilities register\n"); + break; + } + s->data_count = 0; + sdhcv2_start_adma(s); + break; + default: + DPRINT_ERROR("Unsupported DMA type in HOSTCTL register\n"); + break; + } + } else { + if ((s->trnmod & SDHC_TRNS_READ) && sd_data_ready(s->card)) { + s->prnsts |= SDHC_DOING_READ | SDHC_DATA_INHIBIT | + SDHC_DAT_LINE_ACTIVE; + s->data_count = 0; + qemu_mod_timer(s->read_buffer_timer, + qemu_get_clock_ns(vm_clock) + SDHC_READ_BUFFER_DELAY); + } else { + s->prnsts |= SDHC_DOING_WRITE | SDHC_DAT_LINE_ACTIVE | + SDHC_SPACE_AVAILABLE | SDHC_DATA_INHIBIT; + s->data_count = 0; + qemu_mod_timer(s->write_buffer_timer, + qemu_get_clock_ns(vm_clock) + SDHC_WRITE_BUFFER_DELAY); + } + } +} + +static inline bool sdhcv2_sdclk_is_active(SDHCv2State *s) +{ + if (SDHC_CLOCK_IS_ON(s->clkcon) && (s->pwrcon & SDHC_POWER_ON)) { + return true; + } + return false; +} + +void sdhcv2_trigger_command_generation(SDHCv2State *s) +{ + if (!sdhcv2_sdclk_is_active(s)) { + DPRINT_ERROR("Command not issued: SDCLK not active\n"); + return; + } + if (SDHC_COMMAND_TYPE(s->cmdreg) == SDHC_CMD_RESUME || + SDHC_COMMAND_TYPE(s->cmdreg) == SDHC_CMD_SUSPEND) { + DPRINT_ERROR("Command not issued: suspend/resume commands not" + "implemented\n"); + return; + } + if ((s->stoped_state || (s->prnsts & SDHC_DATA_INHIBIT)) && + ((s->cmdreg & SDHC_CMD_DATA_PRESENT) || + ((s->cmdreg & SDHC_CMD_RSP_WITH_BUSY) && + !(SDHC_COMMAND_TYPE(s->cmdreg) == SDHC_CMD_ABORT)))) { + DPRINT_ERROR("Command not issued: DAT line is busy\n"); + return; + } + + if (SDHC_COMMAND_TYPE(s->cmdreg) == SDHC_CMD_ABORT && + TRANSFERRING_DATA(s->prnsts)) { + DPRINT_L2("ABORT command issued: data transfer stopped\n"); + qemu_del_timer(s->read_buffer_timer); /* stop reading data */ + qemu_del_timer(s->write_buffer_timer); /* stop writing data */ + qemu_mod_timer(s->transfer_complete_timer, + qemu_get_clock_ns(vm_clock) + 1); + } + + sdhcv2_send_command(s); + sdhcv2_raise_response_recieved_irq(s); + + if (s->blksize == 0 || !(s->cmdreg & SDHC_CMD_DATA_PRESENT)) { + return; + } + sdhcv2_transfer_data(s); +} + +/* The Buffer Data Port register must be accessed by sequential and + * continuous manner. + * @byte_num - byte number [0..3] of BDATAPORT register to access */ +static inline bool +sdhcv2_buff_access_is_sequential(SDHCv2State *s, unsigned byte_num) +{ + if ((s->data_count & 0x3) != byte_num) { + DPRINT_ERROR("Non-sequential access to Buffer Data Port register" + "is prohibited\n"); + return false; + } + return true; +} + +/* Read byte from SD host controller registers map */ +uint8_t sdhcv2_read_1byte(SDHCv2State *s, target_phys_addr_t offset) +{ + switch (offset) { + case SDHC_SYSAD ... SDHC_SYSAD_END: + return (s->sdmasysad >> 8 * (offset - SDHC_SYSAD)) & 0xFF; + case SDHC_BLKSIZE ... SDHC_BLKSIZE_END: + return (s->blksize >> 8 * (offset - SDHC_BLKSIZE)) & 0xFF; + case SDHC_BLKCNT ... SDHC_BLKCNT_END: + return (s->blkcnt >> 8 * (offset - SDHC_BLKCNT)) & 0xFF; + case SDHC_ARGUMENT ... SDHC_ARGUMENT_END: + return (s->argument >> 8 * (offset - SDHC_ARGUMENT)) & 0xFF; + case SDHC_TRNMOD ... SDHC_TRNMOD_END: + return (s->trnmod >> 8 * (offset - SDHC_TRNMOD)) & 0xFF; + case SDHC_CMDREG ... SDHC_CMDREG_END: + return (s->cmdreg >> 8 * (offset - SDHC_CMDREG)) & 0xFF; + case SDHC_RSPREG0 ... SDHC_RSPREG3_END: + { + int i = (offset - SDHC_RSPREG0) >> 2; + return (s->rspreg[i] >> 8 * (offset - SDHC_RSPREG0 - i * 4)) & 0xFF; + } + case SDHC_BDATA ... SDHC_BDATA_END: + if (sdhcv2_buff_access_is_sequential(s, offset - SDHC_BDATA)) { + return sdhcv2_read_dataport(s, 1); + } + return 0; + case SDHC_PRNSTS ... SDHC_PRNSTS_END: + return (s->prnsts >> 8 * (offset - SDHC_PRNSTS)) & 0xFF; + case SDHC_HOSTCTL: + return s->hostctl; + case SDHC_PWRCON: + return s->pwrcon; + case SDHC_BLKGAP: + return s->blkgap; + case SDHC_WAKCON: + return s->wakcon; + case SDHC_CLKCON ... SDHC_CLKCON_END: + return (s->clkcon >> 8 * (offset - SDHC_CLKCON)) & 0xFF; + case SDHC_TIMEOUTCON: + return s->timeoutcon; + case SDHC_SWRST: + return 0; + case SDHC_SLOT_INT_STATUS ... SDHC_SLOT_INT_STATUS_END: + return (s->slotint >> 8 * (offset - SDHC_SLOT_INT_STATUS)) & 0xFF; + case SDHC_NORINTSTS ... SDHC_NORINTSTS_END: + return (s->norintsts >> 8 * (offset - SDHC_NORINTSTS)) & 0xFF; + case SDHC_ERRINTSTS ... SDHC_ERRINTSTS_END: + return (s->errintsts >> 8 * (offset - SDHC_ERRINTSTS)) & 0xFF; + case SDHC_NORINTSTSEN ... SDHC_NORINTSTSEN_END: + return (s->norintstsen >> 8 * (offset - SDHC_NORINTSTSEN)) & 0xFF; + case SDHC_ERRINTSTSEN ... SDHC_ERRINTSTSEN_END: + return (s->errintstsen >> 8 * (offset - SDHC_ERRINTSTSEN)) & 0xFF; + case SDHC_NORINTSIGEN ... SDHC_NORINTSIGEN_END: + return (s->norintsigen >> 8 * (offset - SDHC_NORINTSIGEN)) & 0xFF; + case SDHC_ERRINTSIGEN ... SDHC_ERRINTSIGEN_END: + return (s->errintsigen >> 8 * (offset - SDHC_ERRINTSIGEN)) & 0xFF; + case SDHC_ACMD12ERRSTS ... SDHC_ACMD12ERRSTS_END: + return (s->acmd12errsts >> 8 * (offset - SDHC_ACMD12ERRSTS)) & 0xFF; + case SDHC_CAPAREG ... SDHC_CAPAREG_END: + return (s->capareg >> 8 * (offset - SDHC_CAPAREG)) & 0xFF; + case SDHC_MAXCURR ... SDHC_MAXCURR_END: + return (s->maxcurr >> 8 * (offset - SDHC_MAXCURR)) & 0xFF; + case SDHC_ADMAERR: + return s->admaerr; + case SDHC_ADMASYSADDR ... SDHC_ADMASYSADDR_END: + return (s->admasysaddr >> 8 * (offset - SDHC_ADMASYSADDR)) & 0xFF; + case SDHC_HCVER ... SDHC_HCVER_END: + return (SD_HOST_SPECv2_VERS >> 8 * (offset - SDHC_HCVER)) & 0xFF; + default: + DPRINT_ERROR("bad byte read offset " TARGET_FMT_plx "\n", offset); + return 0; + } +} + +/* Read two bytes from SD host controller registers map */ +uint16_t sdhcv2_read_2byte(SDHCv2State *s, target_phys_addr_t offset) +{ + switch (offset) { + case SDHC_BLKSIZE: + return s->blksize; + case SDHC_BLKCNT: + return s->blkcnt; + case SDHC_TRNMOD: + return s->trnmod; + case SDHC_CMDREG: + return s->cmdreg; + case SDHC_BDATA ... SDHC_BDATA_END: + if (sdhcv2_buff_access_is_sequential(s, offset - SDHC_BDATA)) { + return sdhcv2_read_dataport(s, 2); + } + return 0; + case SDHC_CLKCON: + return s->clkcon; + case SDHC_NORINTSTS: + return s->norintsts; + case SDHC_ERRINTSTS: + return s->errintsts; + case SDHC_NORINTSTSEN: + return s->norintstsen; + case SDHC_ERRINTSTSEN: + return s->errintstsen; + case SDHC_NORINTSIGEN: + return s->norintsigen; + case SDHC_ERRINTSIGEN: + return s->errintsigen; + case SDHC_ACMD12ERRSTS: + return s->acmd12errsts; + case SDHC_SLOT_INT_STATUS: + return s->slotint; + case SDHC_HCVER: + return SD_HOST_SPECv2_VERS; + } + /* Try to read 2 bytes one by one */ + return (sdhcv2_read_1byte(s, offset + 1) << 8) | + sdhcv2_read_1byte(s, offset); +} + +/* Read four bytes from SD host controller registers map */ +uint32_t sdhcv2_read_4byte(SDHCv2State *s, target_phys_addr_t offset) +{ + switch (offset) { + case SDHC_SYSAD: + return s->sdmasysad; + case SDHC_ARGUMENT: + return s->argument; + case SDHC_RSPREG0: + return s->rspreg[0]; + case SDHC_RSPREG1: + return s->rspreg[1]; + case SDHC_RSPREG2: + return s->rspreg[2]; + case SDHC_RSPREG3: + return s->rspreg[3]; + case SDHC_BDATA: + if (sdhcv2_buff_access_is_sequential(s, 0)) { + return sdhcv2_read_dataport(s, 4); + } + return 0; + case SDHC_PRNSTS: + return s->prnsts; + case SDHC_CAPAREG: + return s->capareg; + case SDHC_MAXCURR: + return s->maxcurr; + case SDHC_ADMASYSADDR: + return s->admasysaddr; + } + /* Try to split one 4-bytes read into two 2-bytes reads */ + return (sdhcv2_read_2byte(s, offset + 2) << 16) | + sdhcv2_read_2byte(s, offset); +} + +static void sdhcv2_update_slotint(SDHCv2State *s) +{ + if (!(s->norintsts & s->norintsigen) && + !(s->errintsts & s->errintsigen) && + !((s->norintsts & SDHC_NIS_INSERT) && + (s->wakcon & SDHC_WKUP_ON_INSERT)) && + !((s->norintsts & SDHC_NIS_REMOVE) && + (s->wakcon & SDHC_WKUP_ON_REMOVE))) { + s->slotint = 0; + } else { + s->slotint = 1; + } +} + +/* Write byte to SD host controller registers map */ +void sdhcv2_write_1byte(SDHCv2State *s, target_phys_addr_t offset, + uint32_t value) +{ + int off; + + switch (offset) { + case SDHC_SYSAD ... (SDHC_SYSAD_END - 1): + off = 8 * (offset - SDHC_SYSAD); + s->sdmasysad = (s->sdmasysad & ~(0xFF << off)) | (value << off); + break; + case SDHC_SYSAD_END: + /* Writing to last byte of sdmasysad register might trigger transfer */ + s->sdmasysad = (s->sdmasysad & ~0xFF000000) | (value << 24); + if (TRANSFERRING_DATA(s->prnsts) && (s->blkcnt != 0) && + sdhcv2_sdclk_is_active(s) && (s->blksize != 0) && + SDHC_DMA_TYPE(s->hostctl) == SDHC_CTRL_SDMA) { + sdhcv2_sdma_transfer_multi_blocks(s); + } + break; + case SDHC_BLKSIZE ... SDHC_BLKSIZE_END: + off = 8 * (offset - SDHC_BLKSIZE); + if (!TRANSFERRING_DATA(s->prnsts)) { + s->blksize = (s->blksize & ~(0xFF << off)) | (value << off); + } + break; + case SDHC_BLKCNT ... SDHC_BLKCNT_END: + off = 8 * (offset - SDHC_BLKCNT); + if (!TRANSFERRING_DATA(s->prnsts)) { + s->blkcnt = (s->blkcnt & ~(0xFF << off)) | (value << off); + } + break; + case SDHC_ARGUMENT ... SDHC_ARGUMENT_END: + off = 8 * (offset - SDHC_ARGUMENT); + s->argument = (s->argument & ~(0xFF << off)) | (value << off); + break; + case SDHC_TRNMOD ... SDHC_TRNMOD_END: + off = 8 * (offset - SDHC_TRNMOD); + s->trnmod = (s->trnmod & ~(0xFF << off)) | (value << off); + /* DMA can be enabled only if it's supported as indicated by + * capabilities register */ + if (!(s->capareg & SDHC_CAN_DO_DMA)) { + s->trnmod &= ~SDHC_TRNS_DMA; + } + break; + case SDHC_CMDREG: + s->cmdreg = (s->cmdreg & ~(0xFF)) | value; + break; + case SDHC_CMDREG_END: + /* Writing to the upper byte of CMDREG triggers SD command generation */ + s->cmdreg = (s->cmdreg & ~(0xFF00)) | (value << 8); + sdhcv2_trigger_command_generation(s); + break; + case SDHC_BDATA ... SDHC_BDATA_END: + if (sdhcv2_buff_access_is_sequential(s, offset - SDHC_BDATA)) { + sdhcv2_write_dataport(s, value, 1); + } + break; + case SDHC_HOSTCTL: + s->hostctl = value; + break; + case SDHC_PWRCON: + /* Check that voltage is supported */ + off = (value >> 1) & 0x7; + if ((off < 5) || !(s->capareg & (1 << (31 - off)))) { + DPRINT_ERROR("Bus voltage is not supported\n"); + value &= SDHC_POWER_ON; + } + if (!(s->prnsts & SDHC_CARD_PRESENT)) { + value &= SDHC_POWER_ON; + } + s->pwrcon = value; + break; + case SDHC_BLKGAP: + if (value & 0x0C) { + DPRINT_ERROR("Read-Wait and interrupt at block gap functions" + " not implemented\n"); + } + + if ((value & SDHC_STOP_AT_GAP_REQ) && + (s->blkgap & SDHC_STOP_AT_GAP_REQ)) { + break; + } + s->blkgap = value & (SDHC_STOP_AT_GAP_REQ); + + if ((value & SDHC_CONTINUE_REQ) && s->stoped_state && + (s->blkgap & SDHC_STOP_AT_GAP_REQ) == 0) { + if (s->stoped_state == sdhc_gap_read) { + s->prnsts |= SDHC_DAT_LINE_ACTIVE | SDHC_DOING_READ; + qemu_mod_timer(s->read_buffer_timer, + qemu_get_clock_ns(vm_clock) + SDHC_READ_BUFFER_DELAY); + } else { + s->prnsts |= SDHC_DAT_LINE_ACTIVE | SDHC_DOING_WRITE; + qemu_mod_timer(s->write_buffer_timer, + qemu_get_clock_ns(vm_clock) + SDHC_READ_BUFFER_DELAY); + } + s->stoped_state = sdhc_not_stoped; + } else if (!s->stoped_state && (value & SDHC_STOP_AT_GAP_REQ)) { + if (s->prnsts & SDHC_DOING_READ) { + s->stoped_state = sdhc_gap_read; + } else if (s->prnsts & SDHC_DOING_WRITE) { + s->stoped_state = sdhc_gap_write; + } + } + break; + case SDHC_WAKCON: + s->wakcon = value; + break; + case SDHC_CLKCON ... SDHC_CLKCON_END: + s->clkcon = (s->clkcon & ~(0xFF)) | value; + if (SDHC_CLOCK_INT_EN & s->clkcon) { + s->clkcon |= SDHC_CLOCK_INT_STABLE; + } else { + s->clkcon &= ~SDHC_CLOCK_INT_STABLE; + } + if (!(s->prnsts & SDHC_CARD_PRESENT)) { + s->clkcon &= SDHC_CLOCK_SDCLK_EN; + } + break; + case SDHC_TIMEOUTCON: + s->timeoutcon = value & 0x0F; + break; + case SDHC_SWRST: + switch (value) { + case SDHC_RESET_ALL: + sdhcv2_reset(s); + break; + case SDHC_RESET_CMD: + s->prnsts &= ~SDHC_CMD_INHIBIT; + s->norintsts &= ~SDHC_NIS_CMDCMP; + break; + case SDHC_RESET_DATA: + s->data_count = 0; + qemu_del_timer(s->read_buffer_timer); + qemu_del_timer(s->write_buffer_timer); + s->prnsts &= ~(SDHC_SPACE_AVAILABLE | SDHC_DATA_AVAILABLE | + SDHC_DOING_READ | SDHC_DOING_WRITE | + SDHC_DATA_INHIBIT | SDHC_DAT_LINE_ACTIVE); + s->blkgap &= ~(SDHC_STOP_AT_GAP_REQ | SDHC_CONTINUE_REQ); + s->stoped_state = sdhc_not_stoped; + s->norintsts &= ~(SDHC_NIS_WBUFRDY | SDHC_NIS_RBUFRDY | + SDHC_NIS_DMA | SDHC_NIS_TRSCMP | SDHC_NIS_BLKGAP); + break; + } + break; + case SDHC_NORINTSTS: + s->norintsts &= ~value; + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_NORINTSTS_END: + if (s->norintstsen & SDHC_NISEN_CARDINT) { + value &= ~(SDHC_NIS_CARDINT >> 8); + } + s->norintsts &= (s->norintsts & SDHC_NIS_ERR) | ~(value << 8); + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_ERRINTSTS ... SDHC_ERRINTSTS_END: + s->errintsts &= ~(value << 8 * (offset - SDHC_ERRINTSTS)); + if (!(s->errintsts & SDHC_EIS_CMD12ERR)) { + s->acmd12errsts = 0; + } + s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) : + (s->norintsts &= ~SDHC_NIS_ERR); + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_NORINTSTSEN ... SDHC_NORINTSTSEN_END: + off = 8 * (offset - SDHC_NORINTSTSEN); + s->norintstsen = (s->norintstsen & ~(0xFF << off)) | (value << off); + s->norintsts &= s->norintstsen | SDHC_NIS_ERR; + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_ERRINTSTSEN ... SDHC_ERRINTSTSEN_END: + off = 8 * (offset - SDHC_ERRINTSTSEN); + s->errintstsen = (s->errintstsen & ~(0xFF << off)) | (value << off); + s->errintsts &= s->errintstsen; + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_NORINTSIGEN ... SDHC_NORINTSIGEN_END: + off = 8 * (offset - SDHC_NORINTSIGEN); + s->norintsigen = (s->norintsigen & ~(0xFF << off)) | (value << off); + break; + case SDHC_ERRINTSIGEN ... SDHC_ERRINTSIGEN_END: + off = 8 * (offset - SDHC_ERRINTSIGEN); + s->errintsigen = (s->errintsigen & ~(0xFF << off)) | (value << off); + break; + case SDHC_FEERR ... SDHC_FEERR_END: + s->errintsts |= (value << 8 * (offset - SDHC_FEERR)) & s->errintstsen; + s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) : + (s->norintsts &= ~SDHC_NIS_ERR); + s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0; + qemu_set_irq(s->irq, s->errintsigen & s->errintsts); + break; + case SDHC_FEAER ... SDHC_FEAER_END: + s->acmd12errsts |= (value << 8 * (offset - SDHC_FEAER)); + s->acmd12errsts ? (s->errintsts |= SDHC_EIS_CMD12ERR), + (s->norintsts |= SDHC_NIS_ERR) : + (s->errintsts &= ~SDHC_EIS_CMD12ERR); + s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0; + qemu_set_irq(s->irq, s->errintsigen & s->errintsts); + break; + case SDHC_ADMAERR: + s->admaerr = value; + break; + case SDHC_ADMASYSADDR ... SDHC_ADMASYSADDR_END: + off = 8 * (offset - SDHC_ADMASYSADDR); + s->admasysaddr = + (s->admasysaddr & ~(0xFFull << off)) | ((uint64_t)value << off); + break; + default: + DPRINT_ERROR("bad byte write offset " TARGET_FMT_plx ", value=" + "%u(0x%x)\n", offset, value, value); + break; + } +} + +/* Write 2 bytes to SD host controller registers map */ +void sdhcv2_write_2byte(SDHCv2State *s, target_phys_addr_t offset, + uint16_t value) +{ + switch (offset) { + case SDHC_BLKSIZE: + if (!TRANSFERRING_DATA(s->prnsts)) { + s->blksize = value; + } + break; + case SDHC_BLKCNT: + if (!TRANSFERRING_DATA(s->prnsts)) { + s->blkcnt = value; + } + break; + case SDHC_TRNMOD: + /* DMA can be enabled only if it's supported as indicated by + * capabilities register */ + if (!(s->capareg & SDHC_CAN_DO_DMA)) { + value &= ~SDHC_TRNS_DMA; + } + s->trnmod = value; + break; + case SDHC_CMDREG: + s->cmdreg = value; + sdhcv2_trigger_command_generation(s); + break; + case SDHC_BDATA ... SDHC_BDATA_END: + if (sdhcv2_buff_access_is_sequential(s, offset - SDHC_BDATA)) { + sdhcv2_write_dataport(s, value, 2); + } + break; + case SDHC_CLKCON: + s->clkcon = value; + if (SDHC_CLOCK_INT_EN & s->clkcon) { + s->clkcon |= SDHC_CLOCK_INT_STABLE; + } else { + s->clkcon &= ~SDHC_CLOCK_INT_STABLE; + } + break; + case SDHC_NORINTSTS: + if (s->norintstsen & SDHC_NISEN_CARDINT) { + value &= ~SDHC_NIS_CARDINT; + } + s->norintsts &= (s->norintsts & SDHC_NIS_ERR) | ~value; + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_ERRINTSTS: + s->errintsts &= ~value; + s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) : + (s->norintsts &= ~SDHC_NIS_ERR); + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_NORINTSTSEN: + s->norintstsen = value; + s->norintsts &= s->norintstsen | SDHC_NIS_ERR; + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_ERRINTSTSEN: + s->errintstsen = value; + s->errintsts &= s->errintstsen; + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_NORINTSIGEN: + s->norintsigen = value; + break; + case SDHC_ERRINTSIGEN: + s->errintsigen = value; + break; + case SDHC_FEAER: + s->acmd12errsts |= value; + s->acmd12errsts ? (s->errintsts |= SDHC_EIS_CMD12ERR), + (s->norintsts |= SDHC_NIS_ERR) : + (s->errintsts &= ~SDHC_EIS_CMD12ERR); + s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0; + qemu_set_irq(s->irq, s->errintsigen & s->errintsts); + break; + case SDHC_FEERR: + s->errintsts |= value & s->errintstsen; + s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) : + (s->norintsts &= ~SDHC_NIS_ERR); + s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0; + qemu_set_irq(s->irq, s->errintsigen & s->errintsts); + break; + default: + /* Try to split 2-bytes write into two 1-byte writes */ + sdhcv2_write_1byte(s, offset, value & 0xFF); + sdhcv2_write_1byte(s, offset + 1, (value >> 8) & 0xFF); + break; + } +} + +/* Write 4 bytes to SD host controller registers map */ +void sdhcv2_write_4byte(SDHCv2State *s, target_phys_addr_t offset, + uint32_t value) +{ + switch (offset) { + case SDHC_SYSAD: + s->sdmasysad = value; + if (TRANSFERRING_DATA(s->prnsts) && (s->blkcnt != 0) && + sdhcv2_sdclk_is_active(s) && (s->blksize != 0) && + SDHC_DMA_TYPE(s->hostctl) == SDHC_CTRL_SDMA) { + sdhcv2_sdma_transfer_multi_blocks(s); + } + break; + case SDHC_ARGUMENT: + s->argument = value; + break; + case SDHC_BDATA: + if (sdhcv2_buff_access_is_sequential(s, 0)) { + sdhcv2_write_dataport(s, value, 4); + } + break; + case SDHC_ADMASYSADDR ... SDHC_ADMASYSADDR_END: + { + int off = 8 * (offset - SDHC_ADMASYSADDR); + s->admasysaddr = (s->admasysaddr & ~(0xFFFFFFFFull << off)) | + ((uint64_t)value << off); + break; + } + default: + /* Try to split 4-bytes write into two 2-byte writes */ + sdhcv2_write_2byte(s, offset, value & 0xFFFF); + sdhcv2_write_2byte(s, offset + 2, (value >> 16) & 0xFFFF); + break; + } +} + +void sdhcv2_initialize(SDHCv2State *s) +{ + DriveInfo *bd; + size_t fifo_len; + + switch (SDHC_CAPAB_BLOCKSIZE(s->capareg)) { + case 0: + fifo_len = 512; + break; + case 1: + fifo_len = 1024; + break; + case 2: + fifo_len = 2048; + break; + default: + hw_error("SDHC: unsupported value for maximum block size\n"); + break; + } + s->fifo_buffer = g_malloc0(fifo_len); + + sysbus_init_irq(&s->busdev, &s->irq); + bd = drive_get_next(IF_SD); + + if (bd) { + DPRINT_L1("SD card inserted: name = %s, sectors = %ld\n", + bd->bdrv->device_name, bd->bdrv->total_sectors); + s->card = sd_init(bd->bdrv, 0); + } else { + DPRINT_L1("No SD card\n"); + s->card = sd_init(NULL, 0); + } + s->eject_cb = qemu_allocate_irqs(sdhcv2_insert_eject_cb, s, 1)[0]; + s->ro_cb = qemu_allocate_irqs(sdhcv2_card_readonly_cb, s, 1)[0]; + sd_set_cb(s->card, s->ro_cb, s->eject_cb); + + s->insert_timer = + qemu_new_timer(vm_clock, SCALE_NS, sdhcv2_raise_insertion_irq, s); + + s->read_buffer_timer = + qemu_new_timer(vm_clock, SCALE_NS, sdhcv2_read_block_from_card, s); + + s->write_buffer_timer = + qemu_new_timer(vm_clock, SCALE_NS, sdhcv2_write_block_to_card, s); + + s->transfer_complete_timer = + qemu_new_timer(vm_clock, SCALE_NS, sdhcv2_transfer_complete_irq, s); +} + +typedef struct SDHCv2GenericState { + SDHCv2State hc; + MemoryRegion iomem; +} SDHCv2GenericState; + +static void sdhcv2_generic_reset(DeviceState *d) +{ + sdhcv2_reset(container_of(d, SDHCv2State, busdev.qdev)); +} + +static uint64_t +sdhcv2_generic_read(void *opaque, target_phys_addr_t offset, unsigned size) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + DPRINT_L2("read %u byte: offset " TARGET_FMT_plx "\n", + size, offset); + + switch (size) { + case 1: + return sdhcv2_read_1byte(s, offset); + case 2: + return sdhcv2_read_2byte(s, offset); + case 4: + return sdhcv2_read_4byte(s, offset); + } + return 0; +} + +static void +sdhcv2_generic_write(void *opaque, target_phys_addr_t offset, uint64_t val, + unsigned size) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + DPRINT_L2("write %u byte: offset " TARGET_FMT_plx " = %lu(0x%lx)\n", + size, offset, val, val); + + switch (size) { + case 1: + sdhcv2_write_1byte(s, offset, val); + break; + case 2: + sdhcv2_write_2byte(s, offset, val); + break; + case 4: + sdhcv2_write_4byte(s, offset, val); + break; + } +} + +static const MemoryRegionOps sdhcv2_generic_mmio_ops = { + .read = sdhcv2_generic_read, + .write = sdhcv2_generic_write, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = false + }, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static int sdhcv2_get_fifolen(void *opaque, int version_id) +{ + SDHCv2State *sd = (SDHCv2State *)opaque; + switch (SDHC_CAPAB_BLOCKSIZE(sd->capareg)) { + case 0: + return 512; + case 1: + return 1024; + case 2: + return 2048; + default: + hw_error("SDHC: unsupported value for maximum block size\n"); + } +} + +const VMStateDescription sdhcv2_vmstate = { + .name = "sdhcv2", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(sdmasysad, SDHCv2State), + VMSTATE_UINT16(blksize, SDHCv2State), + VMSTATE_UINT16(blkcnt, SDHCv2State), + VMSTATE_UINT32(argument, SDHCv2State), + VMSTATE_UINT16(trnmod, SDHCv2State), + VMSTATE_UINT16(cmdreg, SDHCv2State), + VMSTATE_UINT32_ARRAY(rspreg, SDHCv2State, 4), + VMSTATE_UINT32(prnsts, SDHCv2State), + VMSTATE_UINT8(hostctl, SDHCv2State), + VMSTATE_UINT8(pwrcon, SDHCv2State), + VMSTATE_UINT8(blkgap, SDHCv2State), + VMSTATE_UINT8(wakcon, SDHCv2State), + VMSTATE_UINT16(clkcon, SDHCv2State), + VMSTATE_UINT8(timeoutcon, SDHCv2State), + VMSTATE_UINT8(admaerr, SDHCv2State), + VMSTATE_UINT16(norintsts, SDHCv2State), + VMSTATE_UINT16(errintsts, SDHCv2State), + VMSTATE_UINT16(norintstsen, SDHCv2State), + VMSTATE_UINT16(errintstsen, SDHCv2State), + VMSTATE_UINT16(norintsigen, SDHCv2State), + VMSTATE_UINT16(errintsigen, SDHCv2State), + VMSTATE_UINT16(acmd12errsts, SDHCv2State), + VMSTATE_UINT16(data_count, SDHCv2State), + VMSTATE_UINT16(slotint, SDHCv2State), + VMSTATE_UINT64(admasysaddr, SDHCv2State), + VMSTATE_UINT8(stoped_state, SDHCv2State), + VMSTATE_VBUFFER(fifo_buffer, SDHCv2State, 1, NULL, 0, + sdhcv2_get_fifolen), + VMSTATE_TIMER(insert_timer, SDHCv2State), + VMSTATE_TIMER(read_buffer_timer, SDHCv2State), + VMSTATE_TIMER(write_buffer_timer, SDHCv2State), + VMSTATE_TIMER(transfer_complete_timer, SDHCv2State), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription sdhcv2_generic_vmstate = { + .name = "sdhcv2_generic", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(hc, SDHCv2GenericState, 1, sdhcv2_vmstate, SDHCv2State), + VMSTATE_END_OF_LIST() + } +}; + +static int sdhcv2_generic_init(SysBusDevice *dev) +{ + SDHCv2State *s = FROM_SYSBUS(SDHCv2State, dev); + SDHCv2GenericState *hc_gen = DO_UPCAST(SDHCv2GenericState, hc, s); + + sdhcv2_initialize(s); + memory_region_init_io(&hc_gen->iomem, &sdhcv2_generic_mmio_ops, s, + "sdhcv2", SDHC_REGISTERS_MAP_SIZE); + sysbus_init_mmio(dev, &hc_gen->iomem); + + return 0; +} + +static SysBusDeviceInfo sdhcv2_generic_info = { + .init = sdhcv2_generic_init, + .qdev.name = "sdhcv2", + .qdev.size = sizeof(SDHCv2GenericState), + .qdev.vmsd = &sdhcv2_generic_vmstate, + .qdev.reset = sdhcv2_generic_reset, + .qdev.props = (Property[]) { + DEFINE_PROP_HEX32("capareg", SDHCv2GenericState, hc.capareg, + SDHC_CAPAB_REG_DEFAULT), + DEFINE_PROP_HEX32("maxcurr", SDHCv2GenericState, hc.maxcurr, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void sdhcv2_register_devices(void) +{ + sysbus_register_withprop(&sdhcv2_generic_info); +} + +device_init(sdhcv2_register_devices) diff --git a/hw/sdhc_ver2.h b/hw/sdhc_ver2.h new file mode 100644 index 0000000..2b13e16 --- /dev/null +++ b/hw/sdhc_ver2.h @@ -0,0 +1,327 @@ +/* + * SD host controller (SD Host Controller Simplified + * Specification Version 2.00 compliant) emulation + * + * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. + * All rights reserved. + * Contributed by Mitsyanko Igor <i.mitsyanko@samsung.com> + * + * Based on MMC controller for Samsung S5PC1xx-based board emulation + * by Alexey Merkulov and Vladimir Monakhov. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SDHC_VER2_H_ +#define SDHC_VER2_H_ + +#include "qemu-common.h" +#include "sysbus.h" +#include "sd.h" + +/* R/W SDMA System Address register 0x0 */ +#define SDHC_SYSAD 0x00 +#define SDHC_SYSAD_END 0x03 + +/* R/W Host DMA Buffer Boundary and Transfer Block Size Register 0x0 */ +#define SDHC_BLKSIZE 0x04 +#define SDHC_BLKSIZE_END 0x05 + +/* R/W Blocks count for current transfer 0x0 */ +#define SDHC_BLKCNT 0x06 +#define SDHC_BLKCNT_END 0x07 + +/* R/W Command Argument Register 0x0 */ +#define SDHC_ARGUMENT 0x08 +#define SDHC_ARGUMENT_END 0x0B + +/* R/W Transfer Mode Setting Register 0x0 */ +#define SDHC_TRNMOD 0x0C +#define SDHC_TRNMOD_END 0x0D +#define SDHC_TRNS_DMA 0x0001 +#define SDHC_TRNS_BLK_CNT_EN 0x0002 +#define SDHC_TRNS_ACMD12 0x0004 +#define SDHC_TRNS_READ 0x0010 +#define SDHC_TRNS_MULTI 0x0020 + +/* R/W Command Register 0x0 */ +#define SDHC_CMDREG 0x0E +#define SDHC_CMDREG_END 0x0F +#define SDHC_CMD_RSP_WITH_BUSY (3 << 0) +#define SDHC_CMD_DATA_PRESENT (1 << 5) +#define SDHC_CMD_SUSPEND (1 << 6) +#define SDHC_CMD_RESUME (1 << 7) +#define SDHC_CMD_ABORT ((1 << 6)|(1 << 7)) +#define SDHC_CMD_TYPE_MASK ((1 << 6)|(1 << 7)) +#define SDHC_COMMAND_TYPE(x) ((x) & SDHC_CMD_TYPE_MASK) + +/* ROC Response Register 0 0x0 */ +#define SDHC_RSPREG0 0x10 +/* ROC Response Register 1 0x0 */ +#define SDHC_RSPREG1 0x14 +/* ROC Response Register 2 0x0 */ +#define SDHC_RSPREG2 0x18 +/* ROC Response Register 3 0x0 */ +#define SDHC_RSPREG3 0x1C +#define SDHC_RSPREG3_END 0x1F + +/* R/W Buffer Data Register 0x0 */ +#define SDHC_BDATA 0x20 +#define SDHC_BDATA_END 0x23 +#define SDHC_BUFFER_END (-1) + +/* R/ROC Present State Register 0x000A0000 */ +#define SDHC_PRNSTS 0x24 +#define SDHC_PRNSTS_END 0x27 +#define SDHC_CMD_INHIBIT 0x00000001 +#define SDHC_DATA_INHIBIT 0x00000002 +#define SDHC_DAT_LINE_ACTIVE 0x00000004 +#define SDHC_DOING_WRITE 0x00000100 +#define SDHC_DOING_READ 0x00000200 +#define SDHC_SPACE_AVAILABLE 0x00000400 +#define SDHC_DATA_AVAILABLE 0x00000800 +#define SDHC_CARD_PRESENT 0x00010000 +#define SDHC_WRITE_PROTECT 0x00080000 +#define TRANSFERRING_DATA(x) \ + ((x) & (SDHC_DOING_READ | SDHC_DOING_WRITE)) + +/* R/W Host control Register 0x0 */ +#define SDHC_HOSTCTL 0x28 +#define SDHC_CTRL_DMA_CHECK_MASK 0x18 +#define SDHC_CTRL_SDMA 0x00 +#define SDHC_CTRL_ADMA_32 0x10 +#define SDHC_CTRL_ADMA_64 0x18 +#define SDHC_DMA_TYPE(x) ((x) & SDHC_CTRL_DMA_CHECK_MASK) + +/* R/W Power Control Register 0x0 */ +#define SDHC_PWRCON 0x29 +#define SDHC_POWER_ON (1 << 0) + +/* R/W Block Gap Control Register 0x0 */ +#define SDHC_BLKGAP 0x2A +#define SDHC_STOP_AT_GAP_REQ 0x01 +#define SDHC_CONTINUE_REQ 0x02 + +/* R/W WakeUp Control Register 0x0 */ +#define SDHC_WAKCON 0x2B +#define SDHC_WKUP_ON_INSERT (1 << 1) +#define SDHC_WKUP_ON_REMOVE (1 << 2) + +/* CLKCON */ +#define SDHC_CLKCON 0x2C +#define SDHC_CLKCON_END 0x2D +#define SDHC_CLOCK_INT_STABLE 0x0002 +#define SDHC_CLOCK_INT_EN 0x0001 +#define SDHC_CLOCK_SDCLK_EN (1 << 2) +#define SDHC_CLOCK_CHK_MASK 0x0007 +#define SDHC_CLOCK_IS_ON(x) \ + (((x) & SDHC_CLOCK_CHK_MASK) == SDHC_CLOCK_CHK_MASK) + +/* R/W Timeout Control Register 0x0 */ +#define SDHC_TIMEOUTCON 0x2E + +/* R/W Software Reset Register 0x0 */ +#define SDHC_SWRST 0x2F +#define SDHC_RESET_ALL 0x01 +#define SDHC_RESET_CMD 0x02 +#define SDHC_RESET_DATA 0x04 + +/* ROC/RW1C Normal Interrupt Status Register 0x0 */ +#define SDHC_NORINTSTS 0x30 +#define SDHC_NORINTSTS_END 0x31 +#define SDHC_NIS_ERR 0x8000 +#define SDHC_NIS_CMDCMP 0x0001 +#define SDHC_NIS_TRSCMP 0x0002 +#define SDHC_NIS_BLKGAP 0x0004 +#define SDHC_NIS_DMA 0x0008 +#define SDHC_NIS_WBUFRDY 0x0010 +#define SDHC_NIS_RBUFRDY 0x0020 +#define SDHC_NIS_INSERT 0x0040 +#define SDHC_NIS_REMOVE 0x0080 +#define SDHC_NIS_CARDINT 0x0100 + +/* ROC/RW1C Error Interrupt Status Register 0x0 */ +#define SDHC_ERRINTSTS 0x32 +#define SDHC_ERRINTSTS_END 0x33 +#define SDHC_EIS_CMDTIMEOUT 0x0001 +#define SDHC_EIS_BLKGAP 0x0004 +#define SDHC_EIS_CMD12ERR 0x0100 +#define SDHC_EIS_ADMAERR 0x0200 + +/* R/W Normal Interrupt Status Enable Register 0x0 */ +#define SDHC_NORINTSTSEN 0x34 +#define SDHC_NORINTSTSEN_END 0x35 +#define SDHC_NISEN_CMDCMP 0x0001 +#define SDHC_NISEN_TRSCMP 0x0002 +#define SDHC_NISEN_DMA 0x0008 +#define SDHC_NISEN_WBUFRDY 0x0010 +#define SDHC_NISEN_RBUFRDY 0x0020 +#define SDHC_NISEN_INSERT 0x0040 +#define SDHC_NISEN_REMOVE 0x0080 +#define SDHC_NISEN_CARDINT 0x0100 + +/* R/W Error Interrupt Status Enable Register 0x0 */ +#define SDHC_ERRINTSTSEN 0x36 +#define SDHC_ERRINTSTSEN_END 0x37 +#define SDHC_EISEN_CMDTIMEOUT 0x0001 +#define SDHC_EISEN_BLKGAP 0x0004 +#define SDHC_EISEN_ADMAERR 0x0200 + +/* R/W Normal Interrupt Signal Enable Register 0x0 */ +#define SDHC_NORINTSIGEN 0x38 +#define SDHC_NORINTSIGEN_END 0x39 +#define SDHC_NORINTSIG_INSERT (1 << 6) +#define SDHC_NORINTSIG_REMOVE (1 << 7) + +/* R/W Error Interrupt Signal Enable Register 0x0 */ +#define SDHC_ERRINTSIGEN 0x3A +#define SDHC_ERRINTSIGEN_END 0x3B + +/* ROC Auto CMD12 error status register 0x0 */ +#define SDHC_ACMD12ERRSTS 0x3C +#define SDHC_ACMD12ERRSTS_END 0x3D + +/* HWInit Capabilities Register 0x05E80080 */ +#define SDHC_CAPAREG 0x40 +#define SDHC_CAPAREG_END 0x43 +#define SDHC_CAN_DO_DMA 0x00400000 +#define SDHC_CAN_DO_ADMA 0x00080000 +#define SDHC_64_BIT_BUS_SUPPORT (1 << 28) +#define SDHC_CAPAB_BLOCKSIZE(x) (((x) >> 16) & 0x3) + +/* HWInit Maximum Current Capabilities Register 0x0 */ +#define SDHC_MAXCURR 0x48 +#define SDHC_MAXCURR_END 0x4B + +/* W Force Event Auto CMD12 Error Interrupt Register 0x0000 */ +#define SDHC_FEAER 0x50 +#define SDHC_FEAER_END 0x51 +/* W Force Event Error Interrupt Register Error Interrupt 0x0000 */ +#define SDHC_FEERR 0x52 +#define SDHC_FEERR_END 0x53 + +/* R/W ADMA Error Status Register 0x00 */ +#define SDHC_ADMAERR 0x54 +#define SDHC_ADMAERR_LENGTH_MISMATCH (1 << 2) +#define SDHC_ADMAERR_STATE_ST_STOP (0 << 0) +#define SDHC_ADMAERR_STATE_ST_FDS (1 << 0) +#define SDHC_ADMAERR_STATE_ST_TFR (3 << 0) +#define SDHC_ADMAERR_STATE_MASK (3 << 0) + +/* R/W ADMA System Address Register 0x00 */ +#define SDHC_ADMASYSADDR 0x58 +#define SDHC_ADMASYSADDR_END 0x5F +#define SDHC_ADMA_ATTR_ACT_TRAN (1 << 5) +#define SDHC_ADMA_ATTR_ACT_LINK (3 << 4) +#define SDHC_ADMA_ATTR_INT (1 << 2) +#define SDHC_ADMA_ATTR_END (1 << 1) +#define SDHC_ADMA_ATTR_VALID (1 << 0) +#define SDHC_ADMA_ATTR_ACT_MASK ((1 << 4)|(1 << 5)) + +/* Slot interrupt status */ +#define SDHC_SLOT_INT_STATUS 0xFC +#define SDHC_SLOT_INT_STATUS_END 0xFD + +/* HWInit Host Controller Version Register 0x0401 */ +#define SDHC_HCVER 0xFE +#define SDHC_HCVER_END 0xFF +#define SD_HOST_SPECv2_VERS 0x2401 + +#define SDHC_REGISTERS_MAP_SIZE 0x100 +#define SDHC_READ_BUFFER_DELAY 1 +#define SDHC_WRITE_BUFFER_DELAY 2 +#define SDHC_INSERTION_DELAY (get_ticks_per_sec()) +#define SDHC_CMD_RESPONSE (3 << 0) + +enum { + sdhc_not_stoped = 0, /* normal SDHC state */ + sdhc_gap_read = 1, /* SDHC stopped at block gap during read operation */ + sdhc_gap_write = 2 /* SDHC stopped at block gap during write operation */ +}; + +/* SD/MMC host controller state */ +typedef struct SDHCv2State { + SysBusDevice busdev; + SDState *card; + + QEMUTimer *insert_timer; /* timer for 'changing' sd card. */ + QEMUTimer *read_buffer_timer; /* read block of data to controller FIFO */ + QEMUTimer *write_buffer_timer; /* write block of data to card from FIFO */ + QEMUTimer *transfer_complete_timer; /* raise transfer complete irq */ + qemu_irq eject_cb; + qemu_irq ro_cb; + qemu_irq irq; + + uint32_t sdmasysad; /* SDMA System Address register */ + uint16_t blksize; /* Host DMA Buff Boundary and Transfer BlkSize Reg */ + uint16_t blkcnt; /* Blocks count for current transfer */ + uint32_t argument; /* Command Argument Register */ + uint16_t trnmod; /* Transfer Mode Setting Register */ + uint16_t cmdreg; /* Command Register */ + uint32_t rspreg[4]; /* Response Registers 0-3 */ + uint32_t prnsts; /* Present State Register */ + uint8_t hostctl; /* Present State Register */ + uint8_t pwrcon; /* Present State Register */ + uint8_t blkgap; /* Block Gap Control Register */ + uint8_t wakcon; /* WakeUp Control Register */ + uint16_t clkcon; /* Command Register */ + uint8_t timeoutcon; /* Timeout Control Register */ + uint8_t admaerr; /* ADMA Error Status Register */ + uint16_t norintsts; /* Normal Interrupt Status Register */ + uint16_t errintsts; /* Error Interrupt Status Register */ + uint16_t norintstsen; /* Normal Interrupt Status Enable Register */ + uint16_t errintstsen; /* Error Interrupt Status Enable Register */ + uint16_t norintsigen; /* Normal Interrupt Signal Enable Register */ + uint16_t errintsigen; /* Error Interrupt Signal Enable Register */ + uint16_t acmd12errsts; /* Auto CMD12 error status register */ + uint16_t slotint; /* Slot interrupt status register */ + uint64_t admasysaddr; /* ADMA System Address Register */ + + uint32_t capareg; /* Capabilities Register */ + uint32_t maxcurr; /* Maximum Current Capabilities Register */ + uint8_t *fifo_buffer; /* SD host i/o FIFO buffer */ + uint16_t data_count; /* current element in FIFO buffer */ + uint8_t stoped_state; /* Current SDHC state */ + /* Buffer Data Port Register - virtual access point to R and W buffers */ + /* Software Reset Register - always reads as 0 */ + /* Force Event Auto CMD12 Error Interrupt Reg - write only */ + /* Force Event Error Interrupt Register- write only */ + /* RO Host Controller Version Register always reads as 0x2401 */ +} SDHCv2State; + +extern const VMStateDescription sdhcv2_vmstate; + +void sdhcv2_initialize(SDHCv2State *s); +void sdhcv2_reset(SDHCv2State *s); +uint8_t sdhcv2_read_1byte(SDHCv2State *s, target_phys_addr_t offset); +uint16_t sdhcv2_read_2byte(SDHCv2State *s, target_phys_addr_t offset); +uint32_t sdhcv2_read_4byte(SDHCv2State *s, target_phys_addr_t offset); +void sdhcv2_write_1byte(SDHCv2State *s, target_phys_addr_t offset, + uint32_t value); +void sdhcv2_write_2byte(SDHCv2State *s, target_phys_addr_t offset, + uint16_t value); +void sdhcv2_write_4byte(SDHCv2State *s, target_phys_addr_t offset, + uint32_t value); +void sdhcv2_send_command(SDHCv2State *s); +void sdhcv2_raise_response_recieved_irq(SDHCv2State *s); +void sdhcv2_trigger_command_generation(SDHCv2State *s); +void sdhcv2_transfer_data(SDHCv2State *s); +void sdhcv2_sdma_transfer_single_block(SDHCv2State *s); +void sdhcv2_sdma_transfer_multi_blocks(SDHCv2State *s); +void sdhcv2_start_adma(SDHCv2State *s); +void sdhcv2_do_transfer_complete(SDHCv2State *s); +uint32_t sdhcv2_read_dataport(SDHCv2State *s, unsigned size); +void sdhcv2_write_dataport(SDHCv2State *s, uint32_t value, unsigned size); + +#endif /* SDHC_VER2_H_ */ -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 31+ messages in thread
* [Qemu-devel] [PATCH V3 0/5] Improve SD controllers emulation 2011-12-26 10:03 [Qemu-devel] [PATCH 0/3] Improve SD controllers emulation Mitsyanko Igor ` (3 preceding siblings ...) 2011-12-28 12:08 ` [Qemu-devel] [PATCH V2 0/3] Improve SD controllers emulation Mitsyanko Igor @ 2011-12-28 15:32 ` Mitsyanko Igor 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 1/5] vmstate: introduce get_bufsize entry in VMStateField Mitsyanko Igor ` (4 more replies) 4 siblings, 5 replies; 31+ messages in thread From: Mitsyanko Igor @ 2011-12-28 15:32 UTC (permalink / raw) To: qemu-devel Cc: peter.maydell, e.voevodin, quintela, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee, afaerber Changelog v2->v3: - PATCH 2/3 splitted into smaller patches 2-4/5. - SDState structure rearrengment dropped. v1->v2: PATCH 1/3: - .calc_size field replaced with .get_bufsize field in VMStateField; - .size_offset removed completely, macros based on it rewritten to use new .get_bufsize field. PATCH 2/3: - all binary variables in SDState now have bool type; - SDState structure rearranged to ensure alignement; - sd_init(), sd_enable() now receive bool; - new version of PATCH 1/3 now used to save wp_groups array; PATCH 3/3: - DMA transfers modified and now operate with data blocks of BLKSIZE only, like real hardware does; - reset procedure optimized; - new version of PATCH 1/3 now used to save fifo_buffer. First patch of this patch set modifies existing VMStateField interface to ease save/restore functionality implementation for device's dynamically allocated buffers. This modification is used later in second patch to implement SD card's VMStateDescription structure. Third patch adds imlementation of new device: SD host controller fully compliant with "SD host controller specification version 2.00". It also uses first patch modifications. Mitsyanko Igor (5): vmstate: introduce get_bufsize entry in VMStateField hw/sd.c: add SD card save/load support hw/sd.c: convert wp_groups, expecting_acmd and enable to bool hw/sd.c: convert wp_switch and spi to bool hw: Introduce spec. ver. 2.00 compliant SD host controller Makefile.target | 1 + hw/g364fb.c | 7 +- hw/hw.h | 41 +-- hw/m48t59.c | 7 +- hw/mac_nvram.c | 8 +- hw/onenand.c | 7 +- hw/sd.c | 126 +++-- hw/sd.h | 4 +- hw/sdhc_ver2.c | 1569 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/sdhc_ver2.h | 327 ++++++++++++ savevm.c | 10 +- 11 files changed, 2018 insertions(+), 89 deletions(-) create mode 100644 hw/sdhc_ver2.c create mode 100644 hw/sdhc_ver2.h -- 1.7.4.1 ^ permalink raw reply [flat|nested] 31+ messages in thread
* [Qemu-devel] [PATCH V3 1/5] vmstate: introduce get_bufsize entry in VMStateField 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 0/5] Improve SD controllers emulation Mitsyanko Igor @ 2011-12-28 15:32 ` Mitsyanko Igor 2012-01-19 13:40 ` Andreas Färber 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 2/5] hw/sd.c: add SD card save/load support Mitsyanko Igor ` (3 subsequent siblings) 4 siblings, 1 reply; 31+ messages in thread From: Mitsyanko Igor @ 2011-12-28 15:32 UTC (permalink / raw) To: qemu-devel Cc: peter.maydell, Mitsyanko Igor, e.voevodin, quintela, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee, afaerber New get_bufsize field in VMStateField is supposed to help us easily add save/restore support of dynamically allocated buffers in device's states. There are some cases when information about size of dynamically allocated buffer is already presented in specific device's state structure, but in such a form that can not be used with existing VMStateField interface. Currently, we either can get size from another variable in device's state as it is with VMSTATE_VBUFFER_* macros, or we can also multiply value kept in a variable by a constant with VMSTATE_BUFFER_MULTIPLY macro. If we need to perform any other action, we're forced to add additional variable with size information to device state structure with the only intention to use it in VMStateDescription. This approach is not very pretty. Adding extra flags to VMStateFlags enum for every other possible operation with size field seems redundant, and still it would't cover cases when we need to perform a set of operations to get size value. With get_bufsize callback we can calculate size of dynamic array in whichever way we need. We don't need .size_offset field anymore, so we can remove it from VMState Field structure to compensate for extra memory consuption because of get_bufsize addition. Macros VMSTATE_VBUFFER* are modified to use new callback instead of .size_offset. Macro VMSTATE_BUFFER_MULTIPLY and VMFlag VMS_MULTIPLY are removed completely as they are now redundant. Signed-off-by: Mitsyanko Igor <i.mitsyanko@samsung.com> --- hw/g364fb.c | 7 ++++++- hw/hw.h | 41 +++++++---------------------------------- hw/m48t59.c | 7 ++++++- hw/mac_nvram.c | 8 +++++++- hw/onenand.c | 7 ++++++- savevm.c | 10 ++-------- 6 files changed, 34 insertions(+), 46 deletions(-) diff --git a/hw/g364fb.c b/hw/g364fb.c index 34fb08c..1ab36c2 100644 --- a/hw/g364fb.c +++ b/hw/g364fb.c @@ -495,6 +495,11 @@ static int g364fb_post_load(void *opaque, int version_id) return 0; } +static int g364fb_get_vramsize(void *opaque, int version_id) +{ + return ((G364State *)opaque)->vram_size; +} + static const VMStateDescription vmstate_g364fb = { .name = "g364fb", .version_id = 1, @@ -502,7 +507,7 @@ static const VMStateDescription vmstate_g364fb = { .minimum_version_id_old = 1, .post_load = g364fb_post_load, .fields = (VMStateField[]) { - VMSTATE_VBUFFER_UINT32(vram, G364State, 1, NULL, 0, vram_size), + VMSTATE_VBUFFER(vram, G364State, 1, NULL, 0, g364fb_get_vramsize), VMSTATE_BUFFER_UNSAFE(color_palette, G364State, 0, 256 * 3), VMSTATE_BUFFER_UNSAFE(cursor_palette, G364State, 0, 9), VMSTATE_UINT16_ARRAY(cursor, G364State, 512), diff --git a/hw/hw.h b/hw/hw.h index efa04d1..a2a43b6 100644 --- a/hw/hw.h +++ b/hw/hw.h @@ -303,7 +303,6 @@ enum VMStateFlags { VMS_ARRAY_OF_POINTER = 0x040, VMS_VARRAY_UINT16 = 0x080, /* Array with size in uint16_t field */ VMS_VBUFFER = 0x100, /* Buffer with size in int32_t field */ - VMS_MULTIPLY = 0x200, /* multiply "size" field by field_size */ VMS_VARRAY_UINT8 = 0x400, /* Array with size in uint8_t field*/ VMS_VARRAY_UINT32 = 0x800, /* Array with size in uint32_t field*/ }; @@ -315,12 +314,12 @@ typedef struct { size_t start; int num; size_t num_offset; - size_t size_offset; const VMStateInfo *info; enum VMStateFlags flags; const VMStateDescription *vmsd; int version_id; bool (*field_exists)(void *opaque, int version_id); + int (*get_bufsize)(void *opaque, int version_id); } VMStateField; typedef struct VMStateSubsection { @@ -584,34 +583,11 @@ extern const VMStateInfo vmstate_info_unused_buffer; .offset = vmstate_offset_buffer(_state, _field) + _start, \ } -#define VMSTATE_BUFFER_MULTIPLY(_field, _state, _version, _test, _start, _field_size, _multiply) { \ +#define VMSTATE_VBUFFER(_field, _state, _version, _test, _start, _get_bufsize) { \ .name = (stringify(_field)), \ .version_id = (_version), \ .field_exists = (_test), \ - .size_offset = vmstate_offset_value(_state, _field_size, uint32_t),\ - .size = (_multiply), \ - .info = &vmstate_info_buffer, \ - .flags = VMS_VBUFFER|VMS_MULTIPLY, \ - .offset = offsetof(_state, _field), \ - .start = (_start), \ -} - -#define VMSTATE_VBUFFER(_field, _state, _version, _test, _start, _field_size) { \ - .name = (stringify(_field)), \ - .version_id = (_version), \ - .field_exists = (_test), \ - .size_offset = vmstate_offset_value(_state, _field_size, int32_t),\ - .info = &vmstate_info_buffer, \ - .flags = VMS_VBUFFER|VMS_POINTER, \ - .offset = offsetof(_state, _field), \ - .start = (_start), \ -} - -#define VMSTATE_VBUFFER_UINT32(_field, _state, _version, _test, _start, _field_size) { \ - .name = (stringify(_field)), \ - .version_id = (_version), \ - .field_exists = (_test), \ - .size_offset = vmstate_offset_value(_state, _field_size, uint32_t),\ + .get_bufsize = (_get_bufsize), \ .info = &vmstate_info_buffer, \ .flags = VMS_VBUFFER|VMS_POINTER, \ .offset = offsetof(_state, _field), \ @@ -891,14 +867,11 @@ extern const VMStateDescription vmstate_hid_ptr_device; #define VMSTATE_BUFFER_START_MIDDLE(_f, _s, _start) \ VMSTATE_STATIC_BUFFER(_f, _s, 0, NULL, _start, sizeof(typeof_field(_s, _f))) -#define VMSTATE_PARTIAL_VBUFFER(_f, _s, _size) \ - VMSTATE_VBUFFER(_f, _s, 0, NULL, 0, _size) - -#define VMSTATE_PARTIAL_VBUFFER_UINT32(_f, _s, _size) \ - VMSTATE_VBUFFER_UINT32(_f, _s, 0, NULL, 0, _size) +#define VMSTATE_PARTIAL_VBUFFER(_f, _s, _get_bufsize) \ + VMSTATE_VBUFFER(_f, _s, 0, NULL, 0, _get_bufsize) -#define VMSTATE_SUB_VBUFFER(_f, _s, _start, _size) \ - VMSTATE_VBUFFER(_f, _s, 0, NULL, _start, _size) +#define VMSTATE_SUB_VBUFFER(_f, _s, _start, _get_bufsize) \ + VMSTATE_VBUFFER(_f, _s, 0, NULL, _start, _get_bufsize) #define VMSTATE_BUFFER_TEST(_f, _s, _test) \ VMSTATE_STATIC_BUFFER(_f, _s, 0, _test, 0, sizeof(typeof_field(_s, _f))) diff --git a/hw/m48t59.c b/hw/m48t59.c index c043996..4e4c9f3 100644 --- a/hw/m48t59.c +++ b/hw/m48t59.c @@ -582,6 +582,11 @@ static const MemoryRegionOps nvram_ops = { .endianness = DEVICE_NATIVE_ENDIAN, }; +static int m48t59_get_bufsize(void *opaque, int version_id) +{ + return ((M48t59State *)opaque)->size; +} + static const VMStateDescription vmstate_m48t59 = { .name = "m48t59", .version_id = 1, @@ -590,7 +595,7 @@ static const VMStateDescription vmstate_m48t59 = { .fields = (VMStateField[]) { VMSTATE_UINT8(lock, M48t59State), VMSTATE_UINT16(addr, M48t59State), - VMSTATE_VBUFFER_UINT32(buffer, M48t59State, 0, NULL, 0, size), + VMSTATE_VBUFFER(buffer, M48t59State, 0, NULL, 0, m48t59_get_bufsize), VMSTATE_END_OF_LIST() } }; diff --git a/hw/mac_nvram.c b/hw/mac_nvram.c index ed0a2b7..f4367f8 100644 --- a/hw/mac_nvram.c +++ b/hw/mac_nvram.c @@ -100,13 +100,19 @@ static const MemoryRegionOps macio_nvram_ops = { .endianness = DEVICE_NATIVE_ENDIAN, }; +static int macio_nvram_get_datasize(void *opaque, int version_id) +{ + return ((MacIONVRAMState *)opaque)->size; +} + static const VMStateDescription vmstate_macio_nvram = { .name = "macio_nvram", .version_id = 1, .minimum_version_id = 1, .minimum_version_id_old = 1, .fields = (VMStateField[]) { - VMSTATE_VBUFFER_UINT32(data, MacIONVRAMState, 0, NULL, 0, size), + VMSTATE_VBUFFER(data, MacIONVRAMState, 0, NULL, 0, + macio_nvram_get_datasize), VMSTATE_END_OF_LIST() } }; diff --git a/hw/onenand.c b/hw/onenand.c index a9d8d67..3e2016b 100644 --- a/hw/onenand.c +++ b/hw/onenand.c @@ -160,6 +160,11 @@ static int onenand_post_load(void *opaque, int version_id) return 0; } +static int onenand_get_blockwpsize(void *opaque, int version_id) +{ + return ((OneNANDState *)opaque)->blocks; +} + static const VMStateDescription vmstate_onenand = { .name = "onenand", .version_id = 1, @@ -181,7 +186,7 @@ static const VMStateDescription vmstate_onenand = { VMSTATE_UINT16(intstatus, OneNANDState), VMSTATE_UINT16(wpstatus, OneNANDState), VMSTATE_INT32(secs_cur, OneNANDState), - VMSTATE_PARTIAL_VBUFFER(blockwp, OneNANDState, blocks), + VMSTATE_PARTIAL_VBUFFER(blockwp, OneNANDState, onenand_get_blockwpsize), VMSTATE_UINT8(ecc.cp, OneNANDState), VMSTATE_UINT16_ARRAY(ecc.lp, OneNANDState, 2), VMSTATE_UINT16(ecc.count, OneNANDState), diff --git a/savevm.c b/savevm.c index f153c25..831c50a 100644 --- a/savevm.c +++ b/savevm.c @@ -1412,10 +1412,7 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd, int size = field->size; if (field->flags & VMS_VBUFFER) { - size = *(int32_t *)(opaque+field->size_offset); - if (field->flags & VMS_MULTIPLY) { - size *= field->size; - } + size = field->get_bufsize(opaque, version_id); } if (field->flags & VMS_ARRAY) { n_elems = field->num; @@ -1476,10 +1473,7 @@ void vmstate_save_state(QEMUFile *f, const VMStateDescription *vmsd, int size = field->size; if (field->flags & VMS_VBUFFER) { - size = *(int32_t *)(opaque+field->size_offset); - if (field->flags & VMS_MULTIPLY) { - size *= field->size; - } + size = field->get_bufsize(opaque, vmsd->version_id); } if (field->flags & VMS_ARRAY) { n_elems = field->num; -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 31+ messages in thread
* Re: [Qemu-devel] [PATCH V3 1/5] vmstate: introduce get_bufsize entry in VMStateField 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 1/5] vmstate: introduce get_bufsize entry in VMStateField Mitsyanko Igor @ 2012-01-19 13:40 ` Andreas Färber 0 siblings, 0 replies; 31+ messages in thread From: Andreas Färber @ 2012-01-19 13:40 UTC (permalink / raw) To: quintela Cc: peter.maydell, Mitsyanko Igor, e.voevodin, qemu-devel, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee Am 28.12.2011 16:32, schrieb Mitsyanko Igor: > New get_bufsize field in VMStateField is supposed to help us easily add save/restore > support of dynamically allocated buffers in device's states. > There are some cases when information about size of dynamically allocated buffer is > already presented in specific device's state structure, but in such a form that > can not be used with existing VMStateField interface. Currently, we either can get size from > another variable in device's state as it is with VMSTATE_VBUFFER_* macros, or we can > also multiply value kept in a variable by a constant with VMSTATE_BUFFER_MULTIPLY > macro. If we need to perform any other action, we're forced to add additional > variable with size information to device state structure with the only intention > to use it in VMStateDescription. This approach is not very pretty. Adding extra > flags to VMStateFlags enum for every other possible operation with size field > seems redundant, and still it would't cover cases when we need to perform a set of > operations to get size value. > With get_bufsize callback we can calculate size of dynamic array in whichever > way we need. We don't need .size_offset field anymore, so we can remove it from > VMState Field structure to compensate for extra memory consuption because of > get_bufsize addition. Macros VMSTATE_VBUFFER* are modified to use new callback > instead of .size_offset. Macro VMSTATE_BUFFER_MULTIPLY and VMFlag VMS_MULTIPLY > are removed completely as they are now redundant. > > Signed-off-by: Mitsyanko Igor <i.mitsyanko@samsung.com> Ping! Juan, what do you think? Andreas > --- > hw/g364fb.c | 7 ++++++- > hw/hw.h | 41 +++++++---------------------------------- > hw/m48t59.c | 7 ++++++- > hw/mac_nvram.c | 8 +++++++- > hw/onenand.c | 7 ++++++- > savevm.c | 10 ++-------- > 6 files changed, 34 insertions(+), 46 deletions(-) > > diff --git a/hw/g364fb.c b/hw/g364fb.c > index 34fb08c..1ab36c2 100644 > --- a/hw/g364fb.c > +++ b/hw/g364fb.c > @@ -495,6 +495,11 @@ static int g364fb_post_load(void *opaque, int version_id) > return 0; > } > > +static int g364fb_get_vramsize(void *opaque, int version_id) > +{ > + return ((G364State *)opaque)->vram_size; > +} > + > static const VMStateDescription vmstate_g364fb = { > .name = "g364fb", > .version_id = 1, > @@ -502,7 +507,7 @@ static const VMStateDescription vmstate_g364fb = { > .minimum_version_id_old = 1, > .post_load = g364fb_post_load, > .fields = (VMStateField[]) { > - VMSTATE_VBUFFER_UINT32(vram, G364State, 1, NULL, 0, vram_size), > + VMSTATE_VBUFFER(vram, G364State, 1, NULL, 0, g364fb_get_vramsize), > VMSTATE_BUFFER_UNSAFE(color_palette, G364State, 0, 256 * 3), > VMSTATE_BUFFER_UNSAFE(cursor_palette, G364State, 0, 9), > VMSTATE_UINT16_ARRAY(cursor, G364State, 512), > diff --git a/hw/hw.h b/hw/hw.h > index efa04d1..a2a43b6 100644 > --- a/hw/hw.h > +++ b/hw/hw.h > @@ -303,7 +303,6 @@ enum VMStateFlags { > VMS_ARRAY_OF_POINTER = 0x040, > VMS_VARRAY_UINT16 = 0x080, /* Array with size in uint16_t field */ > VMS_VBUFFER = 0x100, /* Buffer with size in int32_t field */ > - VMS_MULTIPLY = 0x200, /* multiply "size" field by field_size */ > VMS_VARRAY_UINT8 = 0x400, /* Array with size in uint8_t field*/ > VMS_VARRAY_UINT32 = 0x800, /* Array with size in uint32_t field*/ > }; > @@ -315,12 +314,12 @@ typedef struct { > size_t start; > int num; > size_t num_offset; > - size_t size_offset; > const VMStateInfo *info; > enum VMStateFlags flags; > const VMStateDescription *vmsd; > int version_id; > bool (*field_exists)(void *opaque, int version_id); > + int (*get_bufsize)(void *opaque, int version_id); > } VMStateField; > > typedef struct VMStateSubsection { > @@ -584,34 +583,11 @@ extern const VMStateInfo vmstate_info_unused_buffer; > .offset = vmstate_offset_buffer(_state, _field) + _start, \ > } > > -#define VMSTATE_BUFFER_MULTIPLY(_field, _state, _version, _test, _start, _field_size, _multiply) { \ > +#define VMSTATE_VBUFFER(_field, _state, _version, _test, _start, _get_bufsize) { \ > .name = (stringify(_field)), \ > .version_id = (_version), \ > .field_exists = (_test), \ > - .size_offset = vmstate_offset_value(_state, _field_size, uint32_t),\ > - .size = (_multiply), \ > - .info = &vmstate_info_buffer, \ > - .flags = VMS_VBUFFER|VMS_MULTIPLY, \ > - .offset = offsetof(_state, _field), \ > - .start = (_start), \ > -} > - > -#define VMSTATE_VBUFFER(_field, _state, _version, _test, _start, _field_size) { \ > - .name = (stringify(_field)), \ > - .version_id = (_version), \ > - .field_exists = (_test), \ > - .size_offset = vmstate_offset_value(_state, _field_size, int32_t),\ > - .info = &vmstate_info_buffer, \ > - .flags = VMS_VBUFFER|VMS_POINTER, \ > - .offset = offsetof(_state, _field), \ > - .start = (_start), \ > -} > - > -#define VMSTATE_VBUFFER_UINT32(_field, _state, _version, _test, _start, _field_size) { \ > - .name = (stringify(_field)), \ > - .version_id = (_version), \ > - .field_exists = (_test), \ > - .size_offset = vmstate_offset_value(_state, _field_size, uint32_t),\ > + .get_bufsize = (_get_bufsize), \ > .info = &vmstate_info_buffer, \ > .flags = VMS_VBUFFER|VMS_POINTER, \ > .offset = offsetof(_state, _field), \ > @@ -891,14 +867,11 @@ extern const VMStateDescription vmstate_hid_ptr_device; > #define VMSTATE_BUFFER_START_MIDDLE(_f, _s, _start) \ > VMSTATE_STATIC_BUFFER(_f, _s, 0, NULL, _start, sizeof(typeof_field(_s, _f))) > > -#define VMSTATE_PARTIAL_VBUFFER(_f, _s, _size) \ > - VMSTATE_VBUFFER(_f, _s, 0, NULL, 0, _size) > - > -#define VMSTATE_PARTIAL_VBUFFER_UINT32(_f, _s, _size) \ > - VMSTATE_VBUFFER_UINT32(_f, _s, 0, NULL, 0, _size) > +#define VMSTATE_PARTIAL_VBUFFER(_f, _s, _get_bufsize) \ > + VMSTATE_VBUFFER(_f, _s, 0, NULL, 0, _get_bufsize) > > -#define VMSTATE_SUB_VBUFFER(_f, _s, _start, _size) \ > - VMSTATE_VBUFFER(_f, _s, 0, NULL, _start, _size) > +#define VMSTATE_SUB_VBUFFER(_f, _s, _start, _get_bufsize) \ > + VMSTATE_VBUFFER(_f, _s, 0, NULL, _start, _get_bufsize) > > #define VMSTATE_BUFFER_TEST(_f, _s, _test) \ > VMSTATE_STATIC_BUFFER(_f, _s, 0, _test, 0, sizeof(typeof_field(_s, _f))) > diff --git a/hw/m48t59.c b/hw/m48t59.c > index c043996..4e4c9f3 100644 > --- a/hw/m48t59.c > +++ b/hw/m48t59.c > @@ -582,6 +582,11 @@ static const MemoryRegionOps nvram_ops = { > .endianness = DEVICE_NATIVE_ENDIAN, > }; > > +static int m48t59_get_bufsize(void *opaque, int version_id) > +{ > + return ((M48t59State *)opaque)->size; > +} > + > static const VMStateDescription vmstate_m48t59 = { > .name = "m48t59", > .version_id = 1, > @@ -590,7 +595,7 @@ static const VMStateDescription vmstate_m48t59 = { > .fields = (VMStateField[]) { > VMSTATE_UINT8(lock, M48t59State), > VMSTATE_UINT16(addr, M48t59State), > - VMSTATE_VBUFFER_UINT32(buffer, M48t59State, 0, NULL, 0, size), > + VMSTATE_VBUFFER(buffer, M48t59State, 0, NULL, 0, m48t59_get_bufsize), > VMSTATE_END_OF_LIST() > } > }; > diff --git a/hw/mac_nvram.c b/hw/mac_nvram.c > index ed0a2b7..f4367f8 100644 > --- a/hw/mac_nvram.c > +++ b/hw/mac_nvram.c > @@ -100,13 +100,19 @@ static const MemoryRegionOps macio_nvram_ops = { > .endianness = DEVICE_NATIVE_ENDIAN, > }; > > +static int macio_nvram_get_datasize(void *opaque, int version_id) > +{ > + return ((MacIONVRAMState *)opaque)->size; > +} > + > static const VMStateDescription vmstate_macio_nvram = { > .name = "macio_nvram", > .version_id = 1, > .minimum_version_id = 1, > .minimum_version_id_old = 1, > .fields = (VMStateField[]) { > - VMSTATE_VBUFFER_UINT32(data, MacIONVRAMState, 0, NULL, 0, size), > + VMSTATE_VBUFFER(data, MacIONVRAMState, 0, NULL, 0, > + macio_nvram_get_datasize), > VMSTATE_END_OF_LIST() > } > }; > diff --git a/hw/onenand.c b/hw/onenand.c > index a9d8d67..3e2016b 100644 > --- a/hw/onenand.c > +++ b/hw/onenand.c > @@ -160,6 +160,11 @@ static int onenand_post_load(void *opaque, int version_id) > return 0; > } > > +static int onenand_get_blockwpsize(void *opaque, int version_id) > +{ > + return ((OneNANDState *)opaque)->blocks; > +} > + > static const VMStateDescription vmstate_onenand = { > .name = "onenand", > .version_id = 1, > @@ -181,7 +186,7 @@ static const VMStateDescription vmstate_onenand = { > VMSTATE_UINT16(intstatus, OneNANDState), > VMSTATE_UINT16(wpstatus, OneNANDState), > VMSTATE_INT32(secs_cur, OneNANDState), > - VMSTATE_PARTIAL_VBUFFER(blockwp, OneNANDState, blocks), > + VMSTATE_PARTIAL_VBUFFER(blockwp, OneNANDState, onenand_get_blockwpsize), > VMSTATE_UINT8(ecc.cp, OneNANDState), > VMSTATE_UINT16_ARRAY(ecc.lp, OneNANDState, 2), > VMSTATE_UINT16(ecc.count, OneNANDState), > diff --git a/savevm.c b/savevm.c > index f153c25..831c50a 100644 > --- a/savevm.c > +++ b/savevm.c > @@ -1412,10 +1412,7 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd, > int size = field->size; > > if (field->flags & VMS_VBUFFER) { > - size = *(int32_t *)(opaque+field->size_offset); > - if (field->flags & VMS_MULTIPLY) { > - size *= field->size; > - } > + size = field->get_bufsize(opaque, version_id); > } > if (field->flags & VMS_ARRAY) { > n_elems = field->num; > @@ -1476,10 +1473,7 @@ void vmstate_save_state(QEMUFile *f, const VMStateDescription *vmsd, > int size = field->size; > > if (field->flags & VMS_VBUFFER) { > - size = *(int32_t *)(opaque+field->size_offset); > - if (field->flags & VMS_MULTIPLY) { > - size *= field->size; > - } > + size = field->get_bufsize(opaque, vmsd->version_id); > } > if (field->flags & VMS_ARRAY) { > n_elems = field->num; -- SUSE LINUX Products GmbH, Maxfeldstr. 5, 90409 Nürnberg, Germany GF: Jeff Hawn, Jennifer Guild, Felix Imendörffer; HRB 16746 AG Nürnberg ^ permalink raw reply [flat|nested] 31+ messages in thread
* [Qemu-devel] [PATCH V3 2/5] hw/sd.c: add SD card save/load support 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 0/5] Improve SD controllers emulation Mitsyanko Igor 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 1/5] vmstate: introduce get_bufsize entry in VMStateField Mitsyanko Igor @ 2011-12-28 15:32 ` Mitsyanko Igor 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 3/5] hw/sd.c: convert wp_groups, expecting_acmd and enable to bool Mitsyanko Igor ` (2 subsequent siblings) 4 siblings, 0 replies; 31+ messages in thread From: Mitsyanko Igor @ 2011-12-28 15:32 UTC (permalink / raw) To: qemu-devel Cc: peter.maydell, Mitsyanko Igor, e.voevodin, quintela, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee, afaerber We couldn't properly implement save/restore functionality of SD host controllers states without SD card's state VMStateDescription implementation. This patch updates SD card emulation to support save/load of card's state. Signed-off-by: Mitsyanko Igor <i.mitsyanko@samsung.com> --- hw/sd.c | 100 +++++++++++++++++++++++++++++++++++++++++++++----------------- 1 files changed, 72 insertions(+), 28 deletions(-) diff --git a/hw/sd.c b/hw/sd.c index 07eb263..3e5628e 100644 --- a/hw/sd.c +++ b/hw/sd.c @@ -54,24 +54,28 @@ typedef enum { sd_illegal = -2, } sd_rsp_type_t; +enum { + sd_inactive, + sd_card_identification_mode, + sd_data_transfer_mode, +}; + +enum { + sd_inactive_state = -1, + sd_idle_state = 0, + sd_ready_state, + sd_identification_state, + sd_standby_state, + sd_transfer_state, + sd_sendingdata_state, + sd_receivingdata_state, + sd_programming_state, + sd_disconnect_state, +} state; + struct SDState { - enum { - sd_inactive, - sd_card_identification_mode, - sd_data_transfer_mode, - } mode; - enum { - sd_inactive_state = -1, - sd_idle_state = 0, - sd_ready_state, - sd_identification_state, - sd_standby_state, - sd_transfer_state, - sd_sendingdata_state, - sd_receivingdata_state, - sd_programming_state, - sd_disconnect_state, - } state; + uint32_t mode; + int32_t state; uint32_t ocr; uint8_t scr[8]; uint8_t cid[16]; @@ -81,22 +85,22 @@ struct SDState { uint8_t sd_status[64]; uint32_t vhs; int wp_switch; - int *wp_groups; + bool *wp_groups; uint64_t size; - int blk_len; + uint32_t blk_len; uint32_t erase_start; uint32_t erase_end; uint8_t pwd[16]; - int pwd_len; - int function_group[6]; + uint32_t pwd_len; + uint8_t function_group[6]; int spi; - int current_cmd; + uint8_t current_cmd; /* True if we will handle the next command as an ACMD. Note that this does * *not* track the APP_CMD status bit! */ - int expecting_acmd; - int blk_written; + bool expecting_acmd; + uint32_t blk_written; uint64_t data_start; uint32_t data_offset; uint8_t data[512]; @@ -105,7 +109,7 @@ struct SDState { BlockDriverState *bdrv; uint8_t *buf; - int enable; + bool enable; }; static void sd_set_mode(SDState *sd) @@ -415,8 +419,8 @@ static void sd_reset(SDState *sd, BlockDriverState *bdrv) if (sd->wp_groups) g_free(sd->wp_groups); sd->wp_switch = bdrv ? bdrv_is_read_only(bdrv) : 0; - sd->wp_groups = (int *) g_malloc0(sizeof(int) * sect); - memset(sd->function_group, 0, sizeof(int) * 6); + sd->wp_groups = g_new0(bool, sect); + memset(sd->function_group, 0, sizeof(sd->function_group)); sd->erase_start = 0; sd->erase_end = 0; sd->size = size; @@ -440,6 +444,45 @@ static const BlockDevOps sd_block_ops = { .change_media_cb = sd_cardchange, }; +static int sd_get_wpgroups_size(void *opaque, int version_id) +{ + SDState *sd = (SDState *)opaque; + return sizeof(bool) * (sd->size >> (HWBLOCK_SHIFT + SECTOR_SHIFT + + WPGROUP_SHIFT)); +} + +static const VMStateDescription sd_vmstate = { + .name = "sd_card", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(mode, SDState), + VMSTATE_INT32(state, SDState), + VMSTATE_UINT8_ARRAY(cid, SDState, 16), + VMSTATE_UINT8_ARRAY(csd, SDState, 16), + VMSTATE_UINT16(rca, SDState), + VMSTATE_UINT32(card_status, SDState), + VMSTATE_PARTIAL_BUFFER(sd_status, SDState, 1), + VMSTATE_UINT32(vhs, SDState), + VMSTATE_VBUFFER(wp_groups, SDState, 1, NULL, 0, sd_get_wpgroups_size), + VMSTATE_UINT32(blk_len, SDState), + VMSTATE_UINT32(erase_start, SDState), + VMSTATE_UINT32(erase_end, SDState), + VMSTATE_UINT8_ARRAY(pwd, SDState, 16), + VMSTATE_UINT32(pwd_len, SDState), + VMSTATE_UINT8_ARRAY(function_group, SDState, 6), + VMSTATE_UINT8(current_cmd, SDState), + VMSTATE_BOOL(expecting_acmd, SDState), + VMSTATE_UINT32(blk_written, SDState), + VMSTATE_UINT64(data_start, SDState), + VMSTATE_UINT32(data_offset, SDState), + VMSTATE_UINT8_ARRAY(data, SDState, 512), + VMSTATE_BUFFER_UNSAFE(buf, SDState, 1, 512), + VMSTATE_BOOL(enable, SDState), + VMSTATE_END_OF_LIST() + } +}; + /* We do not model the chip select pin, so allow the board to select whether card should be in SSI or MMC/SD mode. It is also up to the board to ensure that ssi transfers only occur when the chip select @@ -457,6 +500,7 @@ SDState *sd_init(BlockDriverState *bs, int is_spi) bdrv_attach_dev_nofail(sd->bdrv, sd); bdrv_set_dev_ops(sd->bdrv, &sd_block_ops, sd); } + vmstate_register(NULL, -1, &sd_vmstate, sd); return sd; } @@ -560,7 +604,7 @@ static void sd_lock_command(SDState *sd) sd->card_status |= LOCK_UNLOCK_FAILED; return; } - memset(sd->wp_groups, 0, sizeof(int) * (sd->size >> + memset(sd->wp_groups, 0, sizeof(bool) * (sd->size >> (HWBLOCK_SHIFT + SECTOR_SHIFT + WPGROUP_SHIFT))); sd->csd[14] &= ~0x10; sd->card_status &= ~CARD_IS_LOCKED; -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 31+ messages in thread
* [Qemu-devel] [PATCH V3 3/5] hw/sd.c: convert wp_groups, expecting_acmd and enable to bool 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 0/5] Improve SD controllers emulation Mitsyanko Igor 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 1/5] vmstate: introduce get_bufsize entry in VMStateField Mitsyanko Igor 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 2/5] hw/sd.c: add SD card save/load support Mitsyanko Igor @ 2011-12-28 15:32 ` Mitsyanko Igor 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 4/5] hw/sd.c: convert wp_switch and spi " Mitsyanko Igor 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 5/5] hw: Introduce spec. ver. 2.00 compliant SD host controller Mitsyanko Igor 4 siblings, 0 replies; 31+ messages in thread From: Mitsyanko Igor @ 2011-12-28 15:32 UTC (permalink / raw) To: qemu-devel Cc: peter.maydell, Mitsyanko Igor, e.voevodin, quintela, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee, afaerber SDState variables wp_groups, expecting_acmd and enable are of bool data type but are currently treated as int type variables by rest of the code. This patch updates sd_enable() and sd_wp_addr() so now they explicitly work with bool type, and replaces 0 and 1 with 'false' and 'true' where it's required. Signed-off-by: Mitsyanko Igor <i.mitsyanko@samsung.com> --- hw/sd.c | 18 +++++++++--------- hw/sd.h | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/hw/sd.c b/hw/sd.c index 3e5628e..955f4fb 100644 --- a/hw/sd.c +++ b/hw/sd.c @@ -426,7 +426,7 @@ static void sd_reset(SDState *sd, BlockDriverState *bdrv) sd->size = size; sd->blk_len = 0x200; sd->pwd_len = 0; - sd->expecting_acmd = 0; + sd->expecting_acmd = false; } static void sd_cardchange(void *opaque, bool load) @@ -494,7 +494,7 @@ SDState *sd_init(BlockDriverState *bs, int is_spi) sd = (SDState *) g_malloc0(sizeof(SDState)); sd->buf = qemu_blockalign(bs, 512); sd->spi = is_spi; - sd->enable = 1; + sd->enable = true; sd_reset(sd, bs); if (sd->bdrv) { bdrv_attach_dev_nofail(sd->bdrv, sd); @@ -578,7 +578,7 @@ static void sd_function_switch(SDState *sd, uint32_t arg) sd->data[66] = crc & 0xff; } -static inline int sd_wp_addr(SDState *sd, uint32_t addr) +static inline bool sd_wp_addr(SDState *sd, uint32_t addr) { return sd->wp_groups[addr >> (HWBLOCK_SHIFT + SECTOR_SHIFT + WPGROUP_SHIFT)]; @@ -1052,7 +1052,7 @@ static sd_rsp_type_t sd_normal_command(SDState *sd, sd->state = sd_programming_state; sd->wp_groups[addr >> (HWBLOCK_SHIFT + - SECTOR_SHIFT + WPGROUP_SHIFT)] = 1; + SECTOR_SHIFT + WPGROUP_SHIFT)] = true; /* Bzzzzzzztt .... Operation complete. */ sd->state = sd_transfer_state; return sd_r1b; @@ -1072,7 +1072,7 @@ static sd_rsp_type_t sd_normal_command(SDState *sd, sd->state = sd_programming_state; sd->wp_groups[addr >> (HWBLOCK_SHIFT + - SECTOR_SHIFT + WPGROUP_SHIFT)] = 0; + SECTOR_SHIFT + WPGROUP_SHIFT)] = false; /* Bzzzzzzztt .... Operation complete. */ sd->state = sd_transfer_state; return sd_r1b; @@ -1169,7 +1169,7 @@ static sd_rsp_type_t sd_normal_command(SDState *sd, if (sd->rca != rca) return sd_r0; - sd->expecting_acmd = 1; + sd->expecting_acmd = true; sd->card_status |= APP_CMD; return sd_r1; @@ -1351,7 +1351,7 @@ int sd_do_command(SDState *sd, SDRequest *req, if (sd->card_status & CARD_IS_LOCKED) { if (!cmd_valid_while_locked(sd, req)) { sd->card_status |= ILLEGAL_COMMAND; - sd->expecting_acmd = 0; + sd->expecting_acmd = false; fprintf(stderr, "SD: Card is locked\n"); rtype = sd_illegal; goto send_response; @@ -1362,7 +1362,7 @@ int sd_do_command(SDState *sd, SDRequest *req, sd_set_mode(sd); if (sd->expecting_acmd) { - sd->expecting_acmd = 0; + sd->expecting_acmd = false; rtype = sd_app_command(sd, *req); } else { rtype = sd_normal_command(sd, *req); @@ -1748,7 +1748,7 @@ int sd_data_ready(SDState *sd) return sd->state == sd_sendingdata_state; } -void sd_enable(SDState *sd, int enable) +void sd_enable(SDState *sd, bool enable) { sd->enable = enable; } diff --git a/hw/sd.h b/hw/sd.h index ac4b7c4..f446783 100644 --- a/hw/sd.h +++ b/hw/sd.h @@ -74,6 +74,6 @@ void sd_write_data(SDState *sd, uint8_t value); uint8_t sd_read_data(SDState *sd); void sd_set_cb(SDState *sd, qemu_irq readonly, qemu_irq insert); int sd_data_ready(SDState *sd); -void sd_enable(SDState *sd, int enable); +void sd_enable(SDState *sd, bool enable); #endif /* __hw_sd_h */ -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 31+ messages in thread
* [Qemu-devel] [PATCH V3 4/5] hw/sd.c: convert wp_switch and spi to bool 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 0/5] Improve SD controllers emulation Mitsyanko Igor ` (2 preceding siblings ...) 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 3/5] hw/sd.c: convert wp_groups, expecting_acmd and enable to bool Mitsyanko Igor @ 2011-12-28 15:32 ` Mitsyanko Igor 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 5/5] hw: Introduce spec. ver. 2.00 compliant SD host controller Mitsyanko Igor 4 siblings, 0 replies; 31+ messages in thread From: Mitsyanko Igor @ 2011-12-28 15:32 UTC (permalink / raw) To: qemu-devel Cc: peter.maydell, Mitsyanko Igor, e.voevodin, quintela, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee, afaerber Currently several binary variables in SDState represented as bool type while several other represented as int. This patch converts wp_switch and spi variables to bool and modifies rest of the code to treat this variables as bool instead of int. Signed-off-by: Mitsyanko Igor <i.mitsyanko@samsung.com> --- hw/sd.c | 8 ++++---- hw/sd.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/hw/sd.c b/hw/sd.c index 955f4fb..147a7e0 100644 --- a/hw/sd.c +++ b/hw/sd.c @@ -84,7 +84,7 @@ struct SDState { uint32_t card_status; uint8_t sd_status[64]; uint32_t vhs; - int wp_switch; + bool wp_switch; bool *wp_groups; uint64_t size; uint32_t blk_len; @@ -94,7 +94,7 @@ struct SDState { uint32_t pwd_len; uint8_t function_group[6]; - int spi; + bool spi; uint8_t current_cmd; /* True if we will handle the next command as an ACMD. Note that this does * *not* track the APP_CMD status bit! @@ -418,7 +418,7 @@ static void sd_reset(SDState *sd, BlockDriverState *bdrv) if (sd->wp_groups) g_free(sd->wp_groups); - sd->wp_switch = bdrv ? bdrv_is_read_only(bdrv) : 0; + sd->wp_switch = bdrv ? bdrv_is_read_only(bdrv) : false; sd->wp_groups = g_new0(bool, sect); memset(sd->function_group, 0, sizeof(sd->function_group)); sd->erase_start = 0; @@ -487,7 +487,7 @@ static const VMStateDescription sd_vmstate = { whether card should be in SSI or MMC/SD mode. It is also up to the board to ensure that ssi transfers only occur when the chip select is asserted. */ -SDState *sd_init(BlockDriverState *bs, int is_spi) +SDState *sd_init(BlockDriverState *bs, bool is_spi) { SDState *sd; diff --git a/hw/sd.h b/hw/sd.h index f446783..d25342f 100644 --- a/hw/sd.h +++ b/hw/sd.h @@ -67,7 +67,7 @@ typedef struct { typedef struct SDState SDState; -SDState *sd_init(BlockDriverState *bs, int is_spi); +SDState *sd_init(BlockDriverState *bs, bool is_spi); int sd_do_command(SDState *sd, SDRequest *req, uint8_t *response); void sd_write_data(SDState *sd, uint8_t value); -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 31+ messages in thread
* [Qemu-devel] [PATCH V3 5/5] hw: Introduce spec. ver. 2.00 compliant SD host controller 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 0/5] Improve SD controllers emulation Mitsyanko Igor ` (3 preceding siblings ...) 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 4/5] hw/sd.c: convert wp_switch and spi " Mitsyanko Igor @ 2011-12-28 15:32 ` Mitsyanko Igor 4 siblings, 0 replies; 31+ messages in thread From: Mitsyanko Igor @ 2011-12-28 15:32 UTC (permalink / raw) To: qemu-devel Cc: peter.maydell, Mitsyanko Igor, e.voevodin, quintela, kyungmin.park, d.solodkiy, m.kozlov, jehyung.lee, afaerber This patch adds implementation of "SD specification version 2.00" compliant SD host controller. Also it provides interface to implement SoC-specific controllers on top of this specification-compliant one. Signed-off-by: Mitsyanko Igor <i.mitsyanko@samsung.com> --- Makefile.target | 1 + hw/sdhc_ver2.c | 1569 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/sdhc_ver2.h | 327 ++++++++++++ 3 files changed, 1897 insertions(+), 0 deletions(-) create mode 100644 hw/sdhc_ver2.c create mode 100644 hw/sdhc_ver2.h diff --git a/Makefile.target b/Makefile.target index 3261383..79c33ac 100644 --- a/Makefile.target +++ b/Makefile.target @@ -358,6 +358,7 @@ obj-arm-y += vexpress.o obj-arm-y += strongarm.o obj-arm-y += collie.o obj-arm-y += pl041.o lm4549.o +obj-arm-y += sdhc_ver2.o obj-sh4-y = shix.o r2d.o sh7750.o sh7750_regnames.o tc58128.o obj-sh4-y += sh_timer.o sh_serial.o sh_intc.o sh_pci.o sm501.o diff --git a/hw/sdhc_ver2.c b/hw/sdhc_ver2.c new file mode 100644 index 0000000..1803ae6 --- /dev/null +++ b/hw/sdhc_ver2.c @@ -0,0 +1,1569 @@ +/* + * SD host controller (SD Host Controller Simplified + * Specification Version 2.00 compliant) emulation + * + * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. + * All rights reserved. + * Contributed by Mitsyanko Igor <i.mitsyanko@samsung.com> + * + * Based on MMC controller for Samsung S5PC1xx-based board emulation + * by Alexey Merkulov and Vladimir Monakhov. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "hw.h" +#include "block_int.h" +#include "blockdev.h" +#include "qemu-timer.h" +#include "sdhc_ver2.h" + +/* host controller debug messages */ +#define SDHC_DEBUG 0 + +#if SDHC_DEBUG == 0 + #define DPRINT_L1(fmt, args...) do { } while (0) + #define DPRINT_L2(fmt, args...) do { } while (0) + #define DPRINT_ERROR(fmt, args...) do { } while (0) +#elif SDHC_DEBUG == 1 + #define DPRINT_L1(fmt, args...) \ + do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0) + #define DPRINT_L2(fmt, args...) do { } while (0) + #define DPRINT_ERROR(fmt, args...) \ + do {fprintf(stderr, "QEMU SDHC ERROR: "fmt, ## args); } while (0) +#else + #define DPRINT_L1(fmt, args...) \ + do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0) + #define DPRINT_L2(fmt, args...) \ + do {fprintf(stderr, "QEMU SDHC: "fmt, ## args); } while (0) + #define DPRINT_ERROR(fmt, args...) \ + do {fprintf(stderr, "QEMU SDHC ERROR: "fmt, ## args); } while (0) +#endif + +/* Default SD/MMC host controller features information, which will be + * presented in CAPABILITIES register of generic SD host controller at reset. + * If not stated otherwise: + * 0 - not supported, 1 - supported, other - prohibited. + */ +#define SDHC_CAPAB_64BITBUS 0ul /* 64-bit System Bus Support */ +#define SDHC_CAPAB_18V 1ul /* Voltage support 1.8v */ +#define SDHC_CAPAB_30V 0ul /* Voltage support 3.0v */ +#define SDHC_CAPAB_33V 1ul /* Voltage support 3.3v */ +#define SDHC_CAPAB_SUSPRESUME 0ul /* Suspend/resume support */ +#define SDHC_CAPAB_SDMA 1ul /* SDMA support */ +#define SDHC_CAPAB_HIGHSPEED 1ul /* High speed support */ +#define SDHC_CAPAB_ADMA 1ul /* ADMA2 support */ +/* Maximum host controller R/W buffers size + * Possible values: 512, 1024, 2048 bytes */ +#define SDHC_CAPAB_MAXBLOCKLENGTH 512ul +/* Maximum clock frequency for SDclock in MHz + * value in range 10-63 MHz, 0 - not defined */ +#define SDHC_CAPAB_BASECLKFREQ 0ul +#define SDHC_CAPAB_TOUNIT 1ul /* Timeout clock unit 0 - kHz, 1 - MHz */ +/* Timeout clock frequency 1-63, 0 - not defined */ +#define SDHC_CAPAB_TOCLKFREQ 0ul + +/* Now check all parameters and calculate CAPABILITIES REGISTER value */ +#if SDHC_CAPAB_64BITBUS > 1 || SDHC_CAPAB_18V > 1 || SDHC_CAPAB_30V > 1 ||\ + SDHC_CAPAB_33V > 1 || SDHC_CAPAB_SUSPRESUME > 1 || SDHC_CAPAB_SDMA > 1 ||\ + SDHC_CAPAB_HIGHSPEED > 1 || SDHC_CAPAB_ADMA > 1 || SDHC_CAPAB_TOUNIT > 1 +#error Capabilities features SDHC_CAPAB_x must have value 0 or 1! +#endif + +#if SDHC_CAPAB_MAXBLOCKLENGTH == 512 +#define MAX_BLOCK_LENGTH 0ul +#elif SDHC_CAPAB_MAXBLOCKLENGTH == 1024 +#define MAX_BLOCK_LENGTH 1ul +#elif SDHC_CAPAB_MAXBLOCKLENGTH == 2048 +#define MAX_BLOCK_LENGTH 2ul +#else +#error Max host controller block size can have value 512, 1024 or 2048 only! +#endif + +#if (SDHC_CAPAB_BASECLKFREQ > 0 && SDHC_CAPAB_BASECLKFREQ < 10) || \ + SDHC_CAPAB_BASECLKFREQ > 63 +#error SDclock frequency can have value in range 0, 10-63 only! +#endif + +#if SDHC_CAPAB_TOCLKFREQ > 63 +#error Timeout clock frequency can have value in range 0-63 only! +#endif + +#define SDHC_CAPAB_REG_DEFAULT \ + ((SDHC_CAPAB_64BITBUS<<28)|(SDHC_CAPAB_18V<<26)|\ + (SDHC_CAPAB_30V<<25)|(SDHC_CAPAB_33V<<24)|(SDHC_CAPAB_SUSPRESUME<<23)|\ + (SDHC_CAPAB_SDMA<<22)|(SDHC_CAPAB_HIGHSPEED<<21)|(SDHC_CAPAB_ADMA<<19)|\ + (MAX_BLOCK_LENGTH<<16)|(SDHC_CAPAB_BASECLKFREQ<<8)|(SDHC_CAPAB_TOUNIT<<7)|\ + (SDHC_CAPAB_TOCLKFREQ)) + +static void sdhcv2_transfer_complete_irq(void *opaque) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + + /* free data transfer line */ + s->prnsts &= ~(SDHC_DOING_READ | SDHC_DOING_WRITE | + SDHC_DAT_LINE_ACTIVE | SDHC_DATA_INHIBIT | + SDHC_SPACE_AVAILABLE | SDHC_DATA_AVAILABLE); + + if (s->norintstsen & SDHC_NISEN_TRSCMP) { + s->norintsts |= SDHC_NIS_TRSCMP; + } + s->slotint |= (s->norintsigen & s->norintsts) ? 1 : 0; + qemu_set_irq(s->irq, s->norintsigen & s->norintsts); +} + +/* raise command response received interrupt */ +void sdhcv2_raise_response_recieved_irq(SDHCv2State *s) +{ + DPRINT_L2("raise IRQ response\n"); + + if (s->norintstsen & SDHC_NISEN_CMDCMP) { + s->norintsts |= SDHC_NIS_CMDCMP; + } + + DPRINT_L2("Interrupt request %s\n", (s->norintsts & s->norintsigen) || + (s->errintsts & s->errintsigen) ? "raised" : "lowered"); + s->slotint |= ((s->norintsts & s->norintsigen) || + (s->errintsts & s->errintsigen)) ? 1 : 0; + qemu_set_irq(s->irq, (s->norintsts & s->norintsigen) || + (s->errintsts & s->errintsigen)); +} + +static void sdhcv2_raise_insertion_irq(void *opaque) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + bool int_raised = false; + + if (s->norintsts & SDHC_NIS_REMOVE) { + qemu_mod_timer(s->insert_timer, + qemu_get_clock_ns(vm_clock) + SDHC_INSERTION_DELAY); + } else { + s->prnsts = 0x1ff0000; + if (s->norintstsen & SDHC_NISEN_INSERT) { + s->norintsts |= SDHC_NIS_INSERT; + int_raised = (s->norintsigen & SDHC_NORINTSIG_INSERT) || + (s->wakcon & SDHC_WKUP_ON_INSERT); + s->slotint |= (int_raised ? 1 : 0); + } + qemu_set_irq(s->irq, int_raised); + } +} + +static void sdhcv2_insert_eject_cb(void *opaque, int irq, int level) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + DPRINT_L1("Card state changed: %s!\n", level ? "insert" : "eject"); + bool int_raised = false; + + if (s->norintsts & SDHC_NIS_REMOVE) { + if (level) { + DPRINT_L2("Change card state: timer set!\n"); + qemu_mod_timer(s->insert_timer, + qemu_get_clock_ns(vm_clock) + SDHC_INSERTION_DELAY); + } + } else { + if (level) { + s->prnsts = 0x1ff0000; + if (s->norintstsen & SDHC_NISEN_INSERT) { + s->norintsts |= SDHC_NIS_INSERT; + int_raised = (s->norintsigen & SDHC_NORINTSIG_INSERT) || + (s->wakcon & SDHC_WKUP_ON_INSERT); + s->slotint |= (int_raised ? 1 : 0); + } + } else { + s->prnsts = 0x1fa0000; + s->pwrcon &= ~SDHC_POWER_ON; + s->clkcon &= ~SDHC_CLOCK_SDCLK_EN; + if (s->norintstsen & SDHC_NISEN_REMOVE) { + s->norintsts |= SDHC_NIS_REMOVE; + int_raised = (s->norintsigen & SDHC_NORINTSIG_REMOVE) || + (s->wakcon & SDHC_WKUP_ON_REMOVE); + s->slotint |= (int_raised ? 1 : 0); + } + } + qemu_set_irq(s->irq, int_raised); + } +} + +static void sdhcv2_card_readonly_cb(void *opaque, int irq, int level) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + level ? (s->prnsts &= ~SDHC_WRITE_PROTECT) : + (s->prnsts |= SDHC_WRITE_PROTECT); +} + +void sdhcv2_reset(SDHCv2State *s) +{ + /* Set all registers to 0. Capabilities registers are not cleared + * and assumed to always preserve their value, given to them during + * initialization */ + memset(&s->sdmasysad, 0, (uintptr_t)&s->capareg - (uintptr_t)&s->sdmasysad); + + s->card ? (s->prnsts = 0x1ff0000) : (s->prnsts = 0x1fa0000); + s->data_count = 0; + s->stoped_state = sdhc_not_stoped; +} + +void sdhcv2_send_command(SDHCv2State *s) +{ + SDRequest request; + uint8_t response[16]; + int rlen; + s->errintsts = 0; + s->acmd12errsts = 0; + if (!s->card) { + goto error; + } + + request.cmd = s->cmdreg >> 8; + request.arg = s->argument; + DPRINT_L1("Sending command %u with argument %08x\n", + request.cmd, request.arg); + rlen = sd_do_command(s->card, &request, response); + if (rlen < 0) { + goto error; + } + if ((s->cmdreg & SDHC_CMD_RESPONSE) != 0) { +#define RWORD(n) ((n >= 0 ? (response[n] << 24) : 0) \ + | (response[n + 1] << 16) \ + | (response[n + 2] << 8) \ + | response[n + 3]) + + if ((rlen == 0) || (rlen != 4 && rlen != 16)) { + goto error; + } + + s->rspreg[0] = RWORD(0); + if (rlen == 4) { + s->rspreg[1] = s->rspreg[2] = s->rspreg[3] = 0; + } else { + s->rspreg[0] = RWORD(11); + s->rspreg[1] = RWORD(7); + s->rspreg[2] = RWORD(3); + s->rspreg[3] = RWORD(-1); + } +#undef RWORD + DPRINT_L1("Response received:\n RSPREG[127..96]=0x%08x, RSPREG[95..64]=" + "0x%08x,\n RSPREG[63..32]=0x%08x, RSPREG[31..0]=0x%08x\n", + s->rspreg[3], s->rspreg[2], s->rspreg[1], s->rspreg[0]); + } + return; + +error: + DPRINT_ERROR("Timeout waiting for command response\n"); + if (s->errintstsen & SDHC_EISEN_CMDTIMEOUT) { + s->errintsts |= SDHC_EIS_CMDTIMEOUT; + s->norintsts |= SDHC_NIS_ERR; + } +} + +void sdhcv2_do_transfer_complete(SDHCv2State *s) +{ + /* Automatically send CMD12 to stop transfer if AutoCMD12 enabled */ + if ((s->trnmod & SDHC_TRNS_ACMD12) != 0) { + SDRequest request; + uint8_t response[16]; + + request.cmd = 0x0C; + request.arg = 0; + DPRINT_L1("Automatically issue CMD%d %08x\n", request.cmd, request.arg); + sd_do_command(s->card, &request, response); + /* Auto CMD12 response goes to the upper Response register */ + s->rspreg[3] = (response[0] << 24) | (response[1] << 16) | + (response[2] << 8) | response[3]; + } + /* pend a timer which will raise a transfer complete irq */ + qemu_mod_timer(s->transfer_complete_timer, + qemu_get_clock_ns(vm_clock) + 1); +} + +/* + * Programmed i/o data transfer + */ + +/* Fill host controller's read buffer with BLKSIZE bytes of data from card */ +static void sdhcv2_read_block_from_card(void *opaque) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + int index = 0; + + if ((s->trnmod & SDHC_TRNS_MULTI) && + (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0)) { + return; + } + + for (index = 0; index < (s->blksize & 0x0fff); index++) { + s->fifo_buffer[index] = sd_read_data(s->card); + } + + /* New data now available for READ through Buffer Port Register */ + s->prnsts |= SDHC_DATA_AVAILABLE; + if (s->norintstsen & SDHC_NISEN_RBUFRDY) { + s->norintsts |= SDHC_NIS_RBUFRDY; + } + + /* Clear DAT line active status if that was the last block */ + if ((s->trnmod & SDHC_TRNS_MULTI) == 0 || + ((s->trnmod & SDHC_TRNS_MULTI) && s->blkcnt == 1)) { + s->prnsts &= ~SDHC_DAT_LINE_ACTIVE; + } + + /* If stop at block gap request was set and it's not the last block of + * data - generate Block Event interrupt */ + if (s->stoped_state == sdhc_gap_read && (s->trnmod & SDHC_TRNS_MULTI) && + s->blkcnt != 1) { + s->prnsts &= ~SDHC_DAT_LINE_ACTIVE; + if (s->norintstsen & SDHC_EISEN_BLKGAP) { + s->norintsts |= SDHC_EIS_BLKGAP; + } + } + + s->slotint |= (s->norintsts & s->norintsigen) ? 1 : 0; + qemu_set_irq(s->irq, s->norintsts & s->norintsigen); +} + +/* Read @size byte of data from host controller @s BUFFER DATA PORT register */ +uint32_t sdhcv2_read_dataport(SDHCv2State *s, unsigned size) +{ + uint32_t value = 0; + int i; + + /* first check that a valid data exists in host controller input buffer */ + if ((s->prnsts & SDHC_DATA_AVAILABLE) == 0) { + DPRINT_ERROR("Trying to read from empty buffer\n"); + return 0; + } + + for (i = 0; i < size; i++) { + value |= s->fifo_buffer[s->data_count] << i * 8; + s->data_count++; + /* check if we've read all valid data (blksize bytes) from buffer */ + if ((s->data_count) >= (s->blksize & 0x0fff)) { + DPRINT_L2("All %u bytes of data have been read from input buffer\n", + s->data_count); + s->prnsts &= ~SDHC_DATA_AVAILABLE; /* no more data in a buffer */ + s->data_count = 0; /* next buff read must start at position [0] */ + + if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) { + s->blkcnt--; + } + + /* if that was the last block of data */ + if ((s->trnmod & SDHC_TRNS_MULTI) == 0 || + ((s->trnmod & SDHC_TRNS_MULTI) && + (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0))) { + sdhcv2_do_transfer_complete(s); + } else if (s->stoped_state == sdhc_gap_read && + !(s->prnsts & SDHC_DAT_LINE_ACTIVE)) { + /* stop at gap request */ + sdhcv2_transfer_complete_irq(s); + } else { /* if there are more data, read next block from card */ + qemu_mod_timer(s->read_buffer_timer, + qemu_get_clock_ns(vm_clock) + SDHC_READ_BUFFER_DELAY); + } + break; + } + } + + return value; +} + +/* Write data from host controller FIFO to card */ +static void sdhcv2_write_block_to_card(void *opaque) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + int index = 0; + + if (s->prnsts & SDHC_SPACE_AVAILABLE) { + if (s->norintstsen & SDHC_NISEN_WBUFRDY) { + s->norintsts |= SDHC_NIS_WBUFRDY; + } + s->slotint |= (s->norintsts & s->norintsigen) ? 1 : 0; + qemu_set_irq(s->irq, s->norintsigen & s->norintsts); + return; + } + + if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) { + if (s->blkcnt == 0) { + return; + } else { + s->blkcnt--; + } + } + + for (index = 0; index < (s->blksize & 0x0fff); index++) { + sd_write_data(s->card, s->fifo_buffer[index]); + } + + /* Next data can be written through BUFFER DATORT register */ + s->prnsts |= SDHC_SPACE_AVAILABLE; + if (s->norintstsen & SDHC_NISEN_WBUFRDY) { + s->norintsts |= SDHC_NIS_WBUFRDY; + } + + /* Finish transfer if that was the last block of data */ + if ((s->trnmod & SDHC_TRNS_MULTI) == 0 || + ((s->trnmod & SDHC_TRNS_MULTI) && + (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0))) { + sdhcv2_do_transfer_complete(s); + } + + /* Generate Block Gap Event if requested and if not the last block */ + if (s->stoped_state == sdhc_gap_write && (s->trnmod & SDHC_TRNS_MULTI) && + s->blkcnt > 0) { + s->prnsts &= ~SDHC_DOING_WRITE; + if (s->norintstsen & SDHC_EISEN_BLKGAP) { + s->norintsts |= SDHC_EIS_BLKGAP; + } + qemu_mod_timer(s->transfer_complete_timer, + qemu_get_clock_ns(vm_clock) + 1); + } + + s->slotint |= (s->norintsts & s->norintsigen) ? 1 : 0; + qemu_set_irq(s->irq, s->norintsigen & s->norintsts); +} + +/* Write @size bytes of @value data to host controller @s Buffer Data Port + * register */ +void sdhcv2_write_dataport(SDHCv2State *s, uint32_t value, unsigned size) +{ + unsigned i; + + /* Check that there is free space left in a buffer */ + if (!(s->prnsts & SDHC_SPACE_AVAILABLE)) { + DPRINT_ERROR("Can't write to data buffer: buffer full\n"); + return; + } + + for (i = 0; i < size; i++) { + s->fifo_buffer[s->data_count] = value & 0xFF; + s->data_count++; + value >>= 8; + if (s->data_count >= (s->blksize & 0x0fff)) { + DPRINT_L2("write buffer filled with %u bytes of data\n", + s->data_count); + s->data_count = 0; + s->prnsts &= ~SDHC_SPACE_AVAILABLE; + if (s->prnsts & SDHC_DOING_WRITE) { + qemu_mod_timer(s->write_buffer_timer, + qemu_get_clock_ns(vm_clock) + SDHC_WRITE_BUFFER_DELAY); + } + } + } +} + +/* + * Single DMA data transfer + */ + +/* Multi block SDMA transfer */ +void sdhcv2_sdma_transfer_multi_blocks(SDHCv2State *s) +{ + bool page_aligned = false; + unsigned int n, begin; + const uint16_t block_size = s->blksize & 0x0fff; + uint32_t boundary_chk = 1 << (((s->blksize & 0xf000) >> 12) + 12); + uint32_t boundary_count = boundary_chk - (s->sdmasysad % boundary_chk); + + /* XXX: Some sd/mmc drivers (for example, u-boot-slp) do not account for + * possible stop at page boundary if initial address is not page aligned, + * allow them to work properly */ + if ((s->sdmasysad % boundary_chk) == 0) { + page_aligned = true; + } + + if (s->trnmod & SDHC_TRNS_READ) { + s->prnsts |= SDHC_DOING_READ | SDHC_DATA_INHIBIT | + SDHC_DAT_LINE_ACTIVE; + while (s->blkcnt) { + if (s->data_count == 0) { + for (n = 0; n < block_size; n++) { + s->fifo_buffer[n] = sd_read_data(s->card); + } + } + begin = s->data_count; + if (((boundary_count + begin) < block_size) && page_aligned) { + s->data_count = boundary_count + begin; + boundary_count = 0; + } else { + s->data_count = block_size; + boundary_count -= block_size - begin; + s->blkcnt--; + } + cpu_physical_memory_write(s->sdmasysad, &s->fifo_buffer[begin], + s->data_count - begin); + s->sdmasysad += s->data_count - begin; + if (s->data_count == block_size) { + s->data_count = 0; + } + if (page_aligned && boundary_count == 0) { + break; + } + } + } else { + s->prnsts |= SDHC_DOING_WRITE | SDHC_DATA_INHIBIT | + SDHC_DAT_LINE_ACTIVE; + while (s->blkcnt) { + begin = s->data_count; + if (((boundary_count + begin) < block_size) && page_aligned) { + s->data_count = boundary_count + begin; + boundary_count = 0; + } else { + s->data_count = block_size; + boundary_count -= block_size - begin; + } + cpu_physical_memory_read(s->sdmasysad, + &s->fifo_buffer[begin], s->data_count); + s->sdmasysad += s->data_count - begin; + if (s->data_count == block_size) { + for (n = 0; n < block_size; n++) { + sd_write_data(s->card, s->fifo_buffer[n]); + } + s->data_count = 0; + s->blkcnt--; + } + if (page_aligned && boundary_count == 0) { + break; + } + } + } + + if (s->blkcnt == 0) { + sdhcv2_do_transfer_complete(s); + } else { + if (s->norintstsen & SDHC_NISEN_DMA) { + s->norintsts |= SDHC_NIS_DMA; + } + s->slotint |= (s->norintsts & s->norintsigen) ? 1 : 0; + qemu_set_irq(s->irq, s->norintsigen & s->norintsts); + } +} + +/* single block SDMA transfer */ +void sdhcv2_sdma_transfer_single_block(SDHCv2State *s) +{ + int n; + uint32_t datacnt = s->blksize & 0x0fff; + + if (s->trnmod & SDHC_TRNS_READ) { + for (n = 0; n < datacnt; n++) { + s->fifo_buffer[n] = sd_read_data(s->card); + } + cpu_physical_memory_write(s->sdmasysad, s->fifo_buffer, datacnt); + } else { + cpu_physical_memory_read(s->sdmasysad, s->fifo_buffer, datacnt); + for (n = 0; n < datacnt; n++) { + sd_write_data(s->card, s->fifo_buffer[n]); + } + } + + if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) { + s->blkcnt--; + } + + sdhcv2_do_transfer_complete(s); +} + +/* Advanced DMA data transfer */ +void sdhcv2_start_adma(SDHCv2State *s) +{ + unsigned int n, length, begin; + uint8_t attributes; + target_phys_addr_t entry_addr, address; + const bool is_32bit_adma = SDHC_DMA_TYPE(s->hostctl) == SDHC_CTRL_ADMA_32; + const uint16_t block_size = s->blksize & 0x0fff; + s->admaerr &= ~SDHC_ADMAERR_LENGTH_MISMATCH; + + while (1) { + address = length = attributes = 0; + + entry_addr = is_32bit_adma ? + (s->admasysaddr & 0xFFFFFFFFull) : s->admasysaddr; + /* fetch next entry from descriptor table */ + cpu_physical_memory_read(entry_addr + 4, (uint8_t *)(&address), + is_32bit_adma ? 4 : 8); + cpu_physical_memory_read(entry_addr + 2, (uint8_t *)(&length), 2); + cpu_physical_memory_read(entry_addr, (uint8_t *)(&attributes), 1); + DPRINT_L2("ADMA loop: addr=0x%x, len=%d, attr=%x\n", address, + length_table, attributes); + + if ((attributes & SDHC_ADMA_ATTR_VALID) == 0) { + /* Indicate that error occurred in ST_FDS state */ + s->admaerr &= ~SDHC_ADMAERR_STATE_MASK; + s->admaerr |= SDHC_ADMAERR_STATE_ST_FDS; + + /* Generate ADMA error interrupt */ + if (s->errintstsen & SDHC_EISEN_ADMAERR) { + s->errintsts |= SDHC_EIS_ADMAERR; + s->norintsts |= SDHC_NIS_ERR; + } + s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0; + qemu_set_irq(s->irq, s->errintsigen & s->errintsts); + break; + } + + if (length == 0) { + length = 65536; + } + /* Address must be aligned */ + address &= (is_32bit_adma ? 0xfffffffc : 0xfffffff8); + + switch (attributes & SDHC_ADMA_ATTR_ACT_MASK) { + case SDHC_ADMA_ATTR_ACT_TRAN: /* data transfer */ + if (s->trnmod & SDHC_TRNS_READ) { + while (length) { + if (s->data_count == 0) { + for (n = 0; n < block_size; n++) { + s->fifo_buffer[n] = sd_read_data(s->card); + } + } + begin = s->data_count; + if ((length + begin) < block_size) { + s->data_count = length + begin; + length = 0; + } else { + s->data_count = block_size; + length -= block_size - begin; + } + cpu_physical_memory_write(address, &s->fifo_buffer[begin], + s->data_count - begin); + address += s->data_count - begin; + if (s->data_count == block_size) { + s->data_count = 0; + if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) { + s->blkcnt--; + if (s->blkcnt == 0) { + break; + } + } + } + } + } else { + while (length) { + begin = s->data_count; + if ((length + begin) < block_size) { + s->data_count = length + begin; + length = 0; + } else { + s->data_count = block_size; + length -= block_size - begin; + } + cpu_physical_memory_read(address, + &s->fifo_buffer[begin], s->data_count); + address += s->data_count - begin; + if (s->data_count == block_size) { + for (n = 0; n < block_size; n++) { + sd_write_data(s->card, s->fifo_buffer[n]); + } + s->data_count = 0; + if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) { + s->blkcnt--; + if (s->blkcnt == 0) { + break; + } + } + } + } + } + s->admasysaddr += (is_32bit_adma ? 8 : 12); + break; + case SDHC_ADMA_ATTR_ACT_LINK: /* link to next descriptor table */ + s->admasysaddr = address; + DPRINT_L1("ADMA link: admasysaddr=0x%lx\n", s->admasysaddr); + break; + default: + s->admasysaddr += (is_32bit_adma ? 8 : 12); + break; + } + + /* ADMA transfer terminates if blkcnt == 0 or by END attribute */ + if (((s->trnmod & SDHC_TRNS_BLK_CNT_EN) && + (s->blkcnt == 0)) || (attributes & SDHC_ADMA_ATTR_END)) { + DPRINT_L2("ADMA transfer completed\n"); + if (length || ((attributes & SDHC_ADMA_ATTR_END) && + (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && + s->blkcnt != 0) || ((s->trnmod & SDHC_TRNS_BLK_CNT_EN) && + s->blkcnt == 0 && (attributes & SDHC_ADMA_ATTR_END) == 0)) { + DPRINT_ERROR("SD/MMC host ADMA length mismatch\n"); + s->admaerr |= SDHC_ADMAERR_LENGTH_MISMATCH | + SDHC_ADMAERR_STATE_ST_TFR; + if (s->errintstsen & SDHC_EISEN_ADMAERR) { + DPRINT_ERROR("Set ADMA error flag\n"); + s->errintsts |= SDHC_EIS_ADMAERR; + s->norintsts |= SDHC_NIS_ERR; + } + s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0; + qemu_set_irq(s->irq, s->errintsigen & s->errintsts); + } + sdhcv2_do_transfer_complete(s); + break; + } + + if (attributes & SDHC_ADMA_ATTR_INT) { + DPRINT_L1("ADMA interrupt: admasysaddr=0x%lx\n", s->admasysaddr); + if (s->norintstsen & SDHC_NISEN_DMA) { + s->norintsts |= SDHC_NIS_DMA; + } + s->slotint |= (s->norintsts & s->norintsigen) ? 1 : 0; + qemu_set_irq(s->irq, s->norintsigen & s->norintsts); + break; + } + } +} + +/* Perform data transfer according to controller configuration */ +void sdhcv2_transfer_data(SDHCv2State *s) +{ + if (s->trnmod & SDHC_TRNS_DMA) { + switch (SDHC_DMA_TYPE(s->hostctl)) { + case SDHC_CTRL_SDMA: + if ((s->trnmod & SDHC_TRNS_MULTI) && + (!(s->trnmod & SDHC_TRNS_BLK_CNT_EN) || s->blkcnt == 0)) { + break; + } + + if ((s->blkcnt == 1) || !(s->trnmod & SDHC_TRNS_MULTI)) { + sdhcv2_sdma_transfer_single_block(s); + } else { + s->data_count = 0; + sdhcv2_sdma_transfer_multi_blocks(s); + } + break; + case SDHC_CTRL_ADMA_32: + if (!(s->capareg & SDHC_CAN_DO_ADMA)) { + DPRINT_ERROR("ADMA32 transfer aborted: ADMA not supported as" + "states by Capabilities register\n"); + break; + } + s->data_count = 0; + sdhcv2_start_adma(s); + break; + case SDHC_CTRL_ADMA_64: + if (!(s->capareg & SDHC_CAN_DO_ADMA) || + !(s->capareg & SDHC_64_BIT_BUS_SUPPORT)) { + DPRINT_ERROR("ADMA64 transfer aborted: 64 bit ADMA not " + "supported as states by Capabilities register\n"); + break; + } + s->data_count = 0; + sdhcv2_start_adma(s); + break; + default: + DPRINT_ERROR("Unsupported DMA type in HOSTCTL register\n"); + break; + } + } else { + if ((s->trnmod & SDHC_TRNS_READ) && sd_data_ready(s->card)) { + s->prnsts |= SDHC_DOING_READ | SDHC_DATA_INHIBIT | + SDHC_DAT_LINE_ACTIVE; + s->data_count = 0; + qemu_mod_timer(s->read_buffer_timer, + qemu_get_clock_ns(vm_clock) + SDHC_READ_BUFFER_DELAY); + } else { + s->prnsts |= SDHC_DOING_WRITE | SDHC_DAT_LINE_ACTIVE | + SDHC_SPACE_AVAILABLE | SDHC_DATA_INHIBIT; + s->data_count = 0; + qemu_mod_timer(s->write_buffer_timer, + qemu_get_clock_ns(vm_clock) + SDHC_WRITE_BUFFER_DELAY); + } + } +} + +static inline bool sdhcv2_sdclk_is_active(SDHCv2State *s) +{ + if (SDHC_CLOCK_IS_ON(s->clkcon) && (s->pwrcon & SDHC_POWER_ON)) { + return true; + } + return false; +} + +void sdhcv2_trigger_command_generation(SDHCv2State *s) +{ + if (!sdhcv2_sdclk_is_active(s)) { + DPRINT_ERROR("Command not issued: SDCLK not active\n"); + return; + } + if (SDHC_COMMAND_TYPE(s->cmdreg) == SDHC_CMD_RESUME || + SDHC_COMMAND_TYPE(s->cmdreg) == SDHC_CMD_SUSPEND) { + DPRINT_ERROR("Command not issued: suspend/resume commands not" + "implemented\n"); + return; + } + if ((s->stoped_state || (s->prnsts & SDHC_DATA_INHIBIT)) && + ((s->cmdreg & SDHC_CMD_DATA_PRESENT) || + ((s->cmdreg & SDHC_CMD_RSP_WITH_BUSY) && + !(SDHC_COMMAND_TYPE(s->cmdreg) == SDHC_CMD_ABORT)))) { + DPRINT_ERROR("Command not issued: DAT line is busy\n"); + return; + } + + if (SDHC_COMMAND_TYPE(s->cmdreg) == SDHC_CMD_ABORT && + TRANSFERRING_DATA(s->prnsts)) { + DPRINT_L2("ABORT command issued: data transfer stopped\n"); + qemu_del_timer(s->read_buffer_timer); /* stop reading data */ + qemu_del_timer(s->write_buffer_timer); /* stop writing data */ + qemu_mod_timer(s->transfer_complete_timer, + qemu_get_clock_ns(vm_clock) + 1); + } + + sdhcv2_send_command(s); + sdhcv2_raise_response_recieved_irq(s); + + if (s->blksize == 0 || !(s->cmdreg & SDHC_CMD_DATA_PRESENT)) { + return; + } + sdhcv2_transfer_data(s); +} + +/* The Buffer Data Port register must be accessed by sequential and + * continuous manner. + * @byte_num - byte number [0..3] of BDATAPORT register to access */ +static inline bool +sdhcv2_buff_access_is_sequential(SDHCv2State *s, unsigned byte_num) +{ + if ((s->data_count & 0x3) != byte_num) { + DPRINT_ERROR("Non-sequential access to Buffer Data Port register" + "is prohibited\n"); + return false; + } + return true; +} + +/* Read byte from SD host controller registers map */ +uint8_t sdhcv2_read_1byte(SDHCv2State *s, target_phys_addr_t offset) +{ + switch (offset) { + case SDHC_SYSAD ... SDHC_SYSAD_END: + return (s->sdmasysad >> 8 * (offset - SDHC_SYSAD)) & 0xFF; + case SDHC_BLKSIZE ... SDHC_BLKSIZE_END: + return (s->blksize >> 8 * (offset - SDHC_BLKSIZE)) & 0xFF; + case SDHC_BLKCNT ... SDHC_BLKCNT_END: + return (s->blkcnt >> 8 * (offset - SDHC_BLKCNT)) & 0xFF; + case SDHC_ARGUMENT ... SDHC_ARGUMENT_END: + return (s->argument >> 8 * (offset - SDHC_ARGUMENT)) & 0xFF; + case SDHC_TRNMOD ... SDHC_TRNMOD_END: + return (s->trnmod >> 8 * (offset - SDHC_TRNMOD)) & 0xFF; + case SDHC_CMDREG ... SDHC_CMDREG_END: + return (s->cmdreg >> 8 * (offset - SDHC_CMDREG)) & 0xFF; + case SDHC_RSPREG0 ... SDHC_RSPREG3_END: + { + int i = (offset - SDHC_RSPREG0) >> 2; + return (s->rspreg[i] >> 8 * (offset - SDHC_RSPREG0 - i * 4)) & 0xFF; + } + case SDHC_BDATA ... SDHC_BDATA_END: + if (sdhcv2_buff_access_is_sequential(s, offset - SDHC_BDATA)) { + return sdhcv2_read_dataport(s, 1); + } + return 0; + case SDHC_PRNSTS ... SDHC_PRNSTS_END: + return (s->prnsts >> 8 * (offset - SDHC_PRNSTS)) & 0xFF; + case SDHC_HOSTCTL: + return s->hostctl; + case SDHC_PWRCON: + return s->pwrcon; + case SDHC_BLKGAP: + return s->blkgap; + case SDHC_WAKCON: + return s->wakcon; + case SDHC_CLKCON ... SDHC_CLKCON_END: + return (s->clkcon >> 8 * (offset - SDHC_CLKCON)) & 0xFF; + case SDHC_TIMEOUTCON: + return s->timeoutcon; + case SDHC_SWRST: + return 0; + case SDHC_SLOT_INT_STATUS ... SDHC_SLOT_INT_STATUS_END: + return (s->slotint >> 8 * (offset - SDHC_SLOT_INT_STATUS)) & 0xFF; + case SDHC_NORINTSTS ... SDHC_NORINTSTS_END: + return (s->norintsts >> 8 * (offset - SDHC_NORINTSTS)) & 0xFF; + case SDHC_ERRINTSTS ... SDHC_ERRINTSTS_END: + return (s->errintsts >> 8 * (offset - SDHC_ERRINTSTS)) & 0xFF; + case SDHC_NORINTSTSEN ... SDHC_NORINTSTSEN_END: + return (s->norintstsen >> 8 * (offset - SDHC_NORINTSTSEN)) & 0xFF; + case SDHC_ERRINTSTSEN ... SDHC_ERRINTSTSEN_END: + return (s->errintstsen >> 8 * (offset - SDHC_ERRINTSTSEN)) & 0xFF; + case SDHC_NORINTSIGEN ... SDHC_NORINTSIGEN_END: + return (s->norintsigen >> 8 * (offset - SDHC_NORINTSIGEN)) & 0xFF; + case SDHC_ERRINTSIGEN ... SDHC_ERRINTSIGEN_END: + return (s->errintsigen >> 8 * (offset - SDHC_ERRINTSIGEN)) & 0xFF; + case SDHC_ACMD12ERRSTS ... SDHC_ACMD12ERRSTS_END: + return (s->acmd12errsts >> 8 * (offset - SDHC_ACMD12ERRSTS)) & 0xFF; + case SDHC_CAPAREG ... SDHC_CAPAREG_END: + return (s->capareg >> 8 * (offset - SDHC_CAPAREG)) & 0xFF; + case SDHC_MAXCURR ... SDHC_MAXCURR_END: + return (s->maxcurr >> 8 * (offset - SDHC_MAXCURR)) & 0xFF; + case SDHC_ADMAERR: + return s->admaerr; + case SDHC_ADMASYSADDR ... SDHC_ADMASYSADDR_END: + return (s->admasysaddr >> 8 * (offset - SDHC_ADMASYSADDR)) & 0xFF; + case SDHC_HCVER ... SDHC_HCVER_END: + return (SD_HOST_SPECv2_VERS >> 8 * (offset - SDHC_HCVER)) & 0xFF; + default: + DPRINT_ERROR("bad byte read offset " TARGET_FMT_plx "\n", offset); + return 0; + } +} + +/* Read two bytes from SD host controller registers map */ +uint16_t sdhcv2_read_2byte(SDHCv2State *s, target_phys_addr_t offset) +{ + switch (offset) { + case SDHC_BLKSIZE: + return s->blksize; + case SDHC_BLKCNT: + return s->blkcnt; + case SDHC_TRNMOD: + return s->trnmod; + case SDHC_CMDREG: + return s->cmdreg; + case SDHC_BDATA ... SDHC_BDATA_END: + if (sdhcv2_buff_access_is_sequential(s, offset - SDHC_BDATA)) { + return sdhcv2_read_dataport(s, 2); + } + return 0; + case SDHC_CLKCON: + return s->clkcon; + case SDHC_NORINTSTS: + return s->norintsts; + case SDHC_ERRINTSTS: + return s->errintsts; + case SDHC_NORINTSTSEN: + return s->norintstsen; + case SDHC_ERRINTSTSEN: + return s->errintstsen; + case SDHC_NORINTSIGEN: + return s->norintsigen; + case SDHC_ERRINTSIGEN: + return s->errintsigen; + case SDHC_ACMD12ERRSTS: + return s->acmd12errsts; + case SDHC_SLOT_INT_STATUS: + return s->slotint; + case SDHC_HCVER: + return SD_HOST_SPECv2_VERS; + } + /* Try to read 2 bytes one by one */ + return (sdhcv2_read_1byte(s, offset + 1) << 8) | + sdhcv2_read_1byte(s, offset); +} + +/* Read four bytes from SD host controller registers map */ +uint32_t sdhcv2_read_4byte(SDHCv2State *s, target_phys_addr_t offset) +{ + switch (offset) { + case SDHC_SYSAD: + return s->sdmasysad; + case SDHC_ARGUMENT: + return s->argument; + case SDHC_RSPREG0: + return s->rspreg[0]; + case SDHC_RSPREG1: + return s->rspreg[1]; + case SDHC_RSPREG2: + return s->rspreg[2]; + case SDHC_RSPREG3: + return s->rspreg[3]; + case SDHC_BDATA: + if (sdhcv2_buff_access_is_sequential(s, 0)) { + return sdhcv2_read_dataport(s, 4); + } + return 0; + case SDHC_PRNSTS: + return s->prnsts; + case SDHC_CAPAREG: + return s->capareg; + case SDHC_MAXCURR: + return s->maxcurr; + case SDHC_ADMASYSADDR: + return s->admasysaddr; + } + /* Try to split one 4-bytes read into two 2-bytes reads */ + return (sdhcv2_read_2byte(s, offset + 2) << 16) | + sdhcv2_read_2byte(s, offset); +} + +static void sdhcv2_update_slotint(SDHCv2State *s) +{ + if (!(s->norintsts & s->norintsigen) && + !(s->errintsts & s->errintsigen) && + !((s->norintsts & SDHC_NIS_INSERT) && + (s->wakcon & SDHC_WKUP_ON_INSERT)) && + !((s->norintsts & SDHC_NIS_REMOVE) && + (s->wakcon & SDHC_WKUP_ON_REMOVE))) { + s->slotint = 0; + } else { + s->slotint = 1; + } +} + +/* Write byte to SD host controller registers map */ +void sdhcv2_write_1byte(SDHCv2State *s, target_phys_addr_t offset, + uint32_t value) +{ + int off; + + switch (offset) { + case SDHC_SYSAD ... (SDHC_SYSAD_END - 1): + off = 8 * (offset - SDHC_SYSAD); + s->sdmasysad = (s->sdmasysad & ~(0xFF << off)) | (value << off); + break; + case SDHC_SYSAD_END: + /* Writing to last byte of sdmasysad register might trigger transfer */ + s->sdmasysad = (s->sdmasysad & ~0xFF000000) | (value << 24); + if (TRANSFERRING_DATA(s->prnsts) && (s->blkcnt != 0) && + sdhcv2_sdclk_is_active(s) && (s->blksize != 0) && + SDHC_DMA_TYPE(s->hostctl) == SDHC_CTRL_SDMA) { + sdhcv2_sdma_transfer_multi_blocks(s); + } + break; + case SDHC_BLKSIZE ... SDHC_BLKSIZE_END: + off = 8 * (offset - SDHC_BLKSIZE); + if (!TRANSFERRING_DATA(s->prnsts)) { + s->blksize = (s->blksize & ~(0xFF << off)) | (value << off); + } + break; + case SDHC_BLKCNT ... SDHC_BLKCNT_END: + off = 8 * (offset - SDHC_BLKCNT); + if (!TRANSFERRING_DATA(s->prnsts)) { + s->blkcnt = (s->blkcnt & ~(0xFF << off)) | (value << off); + } + break; + case SDHC_ARGUMENT ... SDHC_ARGUMENT_END: + off = 8 * (offset - SDHC_ARGUMENT); + s->argument = (s->argument & ~(0xFF << off)) | (value << off); + break; + case SDHC_TRNMOD ... SDHC_TRNMOD_END: + off = 8 * (offset - SDHC_TRNMOD); + s->trnmod = (s->trnmod & ~(0xFF << off)) | (value << off); + /* DMA can be enabled only if it's supported as indicated by + * capabilities register */ + if (!(s->capareg & SDHC_CAN_DO_DMA)) { + s->trnmod &= ~SDHC_TRNS_DMA; + } + break; + case SDHC_CMDREG: + s->cmdreg = (s->cmdreg & ~(0xFF)) | value; + break; + case SDHC_CMDREG_END: + /* Writing to the upper byte of CMDREG triggers SD command generation */ + s->cmdreg = (s->cmdreg & ~(0xFF00)) | (value << 8); + sdhcv2_trigger_command_generation(s); + break; + case SDHC_BDATA ... SDHC_BDATA_END: + if (sdhcv2_buff_access_is_sequential(s, offset - SDHC_BDATA)) { + sdhcv2_write_dataport(s, value, 1); + } + break; + case SDHC_HOSTCTL: + s->hostctl = value; + break; + case SDHC_PWRCON: + /* Check that voltage is supported */ + off = (value >> 1) & 0x7; + if ((off < 5) || !(s->capareg & (1 << (31 - off)))) { + DPRINT_ERROR("Bus voltage is not supported\n"); + value &= SDHC_POWER_ON; + } + if (!(s->prnsts & SDHC_CARD_PRESENT)) { + value &= SDHC_POWER_ON; + } + s->pwrcon = value; + break; + case SDHC_BLKGAP: + if (value & 0x0C) { + DPRINT_ERROR("Read-Wait and interrupt at block gap functions" + " not implemented\n"); + } + + if ((value & SDHC_STOP_AT_GAP_REQ) && + (s->blkgap & SDHC_STOP_AT_GAP_REQ)) { + break; + } + s->blkgap = value & (SDHC_STOP_AT_GAP_REQ); + + if ((value & SDHC_CONTINUE_REQ) && s->stoped_state && + (s->blkgap & SDHC_STOP_AT_GAP_REQ) == 0) { + if (s->stoped_state == sdhc_gap_read) { + s->prnsts |= SDHC_DAT_LINE_ACTIVE | SDHC_DOING_READ; + qemu_mod_timer(s->read_buffer_timer, + qemu_get_clock_ns(vm_clock) + SDHC_READ_BUFFER_DELAY); + } else { + s->prnsts |= SDHC_DAT_LINE_ACTIVE | SDHC_DOING_WRITE; + qemu_mod_timer(s->write_buffer_timer, + qemu_get_clock_ns(vm_clock) + SDHC_READ_BUFFER_DELAY); + } + s->stoped_state = sdhc_not_stoped; + } else if (!s->stoped_state && (value & SDHC_STOP_AT_GAP_REQ)) { + if (s->prnsts & SDHC_DOING_READ) { + s->stoped_state = sdhc_gap_read; + } else if (s->prnsts & SDHC_DOING_WRITE) { + s->stoped_state = sdhc_gap_write; + } + } + break; + case SDHC_WAKCON: + s->wakcon = value; + break; + case SDHC_CLKCON ... SDHC_CLKCON_END: + s->clkcon = (s->clkcon & ~(0xFF)) | value; + if (SDHC_CLOCK_INT_EN & s->clkcon) { + s->clkcon |= SDHC_CLOCK_INT_STABLE; + } else { + s->clkcon &= ~SDHC_CLOCK_INT_STABLE; + } + if (!(s->prnsts & SDHC_CARD_PRESENT)) { + s->clkcon &= SDHC_CLOCK_SDCLK_EN; + } + break; + case SDHC_TIMEOUTCON: + s->timeoutcon = value & 0x0F; + break; + case SDHC_SWRST: + switch (value) { + case SDHC_RESET_ALL: + sdhcv2_reset(s); + break; + case SDHC_RESET_CMD: + s->prnsts &= ~SDHC_CMD_INHIBIT; + s->norintsts &= ~SDHC_NIS_CMDCMP; + break; + case SDHC_RESET_DATA: + s->data_count = 0; + qemu_del_timer(s->read_buffer_timer); + qemu_del_timer(s->write_buffer_timer); + s->prnsts &= ~(SDHC_SPACE_AVAILABLE | SDHC_DATA_AVAILABLE | + SDHC_DOING_READ | SDHC_DOING_WRITE | + SDHC_DATA_INHIBIT | SDHC_DAT_LINE_ACTIVE); + s->blkgap &= ~(SDHC_STOP_AT_GAP_REQ | SDHC_CONTINUE_REQ); + s->stoped_state = sdhc_not_stoped; + s->norintsts &= ~(SDHC_NIS_WBUFRDY | SDHC_NIS_RBUFRDY | + SDHC_NIS_DMA | SDHC_NIS_TRSCMP | SDHC_NIS_BLKGAP); + break; + } + break; + case SDHC_NORINTSTS: + s->norintsts &= ~value; + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_NORINTSTS_END: + if (s->norintstsen & SDHC_NISEN_CARDINT) { + value &= ~(SDHC_NIS_CARDINT >> 8); + } + s->norintsts &= (s->norintsts & SDHC_NIS_ERR) | ~(value << 8); + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_ERRINTSTS ... SDHC_ERRINTSTS_END: + s->errintsts &= ~(value << 8 * (offset - SDHC_ERRINTSTS)); + if (!(s->errintsts & SDHC_EIS_CMD12ERR)) { + s->acmd12errsts = 0; + } + s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) : + (s->norintsts &= ~SDHC_NIS_ERR); + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_NORINTSTSEN ... SDHC_NORINTSTSEN_END: + off = 8 * (offset - SDHC_NORINTSTSEN); + s->norintstsen = (s->norintstsen & ~(0xFF << off)) | (value << off); + s->norintsts &= s->norintstsen | SDHC_NIS_ERR; + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_ERRINTSTSEN ... SDHC_ERRINTSTSEN_END: + off = 8 * (offset - SDHC_ERRINTSTSEN); + s->errintstsen = (s->errintstsen & ~(0xFF << off)) | (value << off); + s->errintsts &= s->errintstsen; + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_NORINTSIGEN ... SDHC_NORINTSIGEN_END: + off = 8 * (offset - SDHC_NORINTSIGEN); + s->norintsigen = (s->norintsigen & ~(0xFF << off)) | (value << off); + break; + case SDHC_ERRINTSIGEN ... SDHC_ERRINTSIGEN_END: + off = 8 * (offset - SDHC_ERRINTSIGEN); + s->errintsigen = (s->errintsigen & ~(0xFF << off)) | (value << off); + break; + case SDHC_FEERR ... SDHC_FEERR_END: + s->errintsts |= (value << 8 * (offset - SDHC_FEERR)) & s->errintstsen; + s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) : + (s->norintsts &= ~SDHC_NIS_ERR); + s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0; + qemu_set_irq(s->irq, s->errintsigen & s->errintsts); + break; + case SDHC_FEAER ... SDHC_FEAER_END: + s->acmd12errsts |= (value << 8 * (offset - SDHC_FEAER)); + s->acmd12errsts ? (s->errintsts |= SDHC_EIS_CMD12ERR), + (s->norintsts |= SDHC_NIS_ERR) : + (s->errintsts &= ~SDHC_EIS_CMD12ERR); + s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0; + qemu_set_irq(s->irq, s->errintsigen & s->errintsts); + break; + case SDHC_ADMAERR: + s->admaerr = value; + break; + case SDHC_ADMASYSADDR ... SDHC_ADMASYSADDR_END: + off = 8 * (offset - SDHC_ADMASYSADDR); + s->admasysaddr = + (s->admasysaddr & ~(0xFFull << off)) | ((uint64_t)value << off); + break; + default: + DPRINT_ERROR("bad byte write offset " TARGET_FMT_plx ", value=" + "%u(0x%x)\n", offset, value, value); + break; + } +} + +/* Write 2 bytes to SD host controller registers map */ +void sdhcv2_write_2byte(SDHCv2State *s, target_phys_addr_t offset, + uint16_t value) +{ + switch (offset) { + case SDHC_BLKSIZE: + if (!TRANSFERRING_DATA(s->prnsts)) { + s->blksize = value; + } + break; + case SDHC_BLKCNT: + if (!TRANSFERRING_DATA(s->prnsts)) { + s->blkcnt = value; + } + break; + case SDHC_TRNMOD: + /* DMA can be enabled only if it's supported as indicated by + * capabilities register */ + if (!(s->capareg & SDHC_CAN_DO_DMA)) { + value &= ~SDHC_TRNS_DMA; + } + s->trnmod = value; + break; + case SDHC_CMDREG: + s->cmdreg = value; + sdhcv2_trigger_command_generation(s); + break; + case SDHC_BDATA ... SDHC_BDATA_END: + if (sdhcv2_buff_access_is_sequential(s, offset - SDHC_BDATA)) { + sdhcv2_write_dataport(s, value, 2); + } + break; + case SDHC_CLKCON: + s->clkcon = value; + if (SDHC_CLOCK_INT_EN & s->clkcon) { + s->clkcon |= SDHC_CLOCK_INT_STABLE; + } else { + s->clkcon &= ~SDHC_CLOCK_INT_STABLE; + } + break; + case SDHC_NORINTSTS: + if (s->norintstsen & SDHC_NISEN_CARDINT) { + value &= ~SDHC_NIS_CARDINT; + } + s->norintsts &= (s->norintsts & SDHC_NIS_ERR) | ~value; + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_ERRINTSTS: + s->errintsts &= ~value; + s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) : + (s->norintsts &= ~SDHC_NIS_ERR); + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_NORINTSTSEN: + s->norintstsen = value; + s->norintsts &= s->norintstsen | SDHC_NIS_ERR; + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_ERRINTSTSEN: + s->errintstsen = value; + s->errintsts &= s->errintstsen; + sdhcv2_update_slotint(s); + qemu_set_irq(s->irq, s->slotint); + break; + case SDHC_NORINTSIGEN: + s->norintsigen = value; + break; + case SDHC_ERRINTSIGEN: + s->errintsigen = value; + break; + case SDHC_FEAER: + s->acmd12errsts |= value; + s->acmd12errsts ? (s->errintsts |= SDHC_EIS_CMD12ERR), + (s->norintsts |= SDHC_NIS_ERR) : + (s->errintsts &= ~SDHC_EIS_CMD12ERR); + s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0; + qemu_set_irq(s->irq, s->errintsigen & s->errintsts); + break; + case SDHC_FEERR: + s->errintsts |= value & s->errintstsen; + s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) : + (s->norintsts &= ~SDHC_NIS_ERR); + s->slotint |= (s->errintsigen & s->errintsts) ? 1 : 0; + qemu_set_irq(s->irq, s->errintsigen & s->errintsts); + break; + default: + /* Try to split 2-bytes write into two 1-byte writes */ + sdhcv2_write_1byte(s, offset, value & 0xFF); + sdhcv2_write_1byte(s, offset + 1, (value >> 8) & 0xFF); + break; + } +} + +/* Write 4 bytes to SD host controller registers map */ +void sdhcv2_write_4byte(SDHCv2State *s, target_phys_addr_t offset, + uint32_t value) +{ + switch (offset) { + case SDHC_SYSAD: + s->sdmasysad = value; + if (TRANSFERRING_DATA(s->prnsts) && (s->blkcnt != 0) && + sdhcv2_sdclk_is_active(s) && (s->blksize != 0) && + SDHC_DMA_TYPE(s->hostctl) == SDHC_CTRL_SDMA) { + sdhcv2_sdma_transfer_multi_blocks(s); + } + break; + case SDHC_ARGUMENT: + s->argument = value; + break; + case SDHC_BDATA: + if (sdhcv2_buff_access_is_sequential(s, 0)) { + sdhcv2_write_dataport(s, value, 4); + } + break; + case SDHC_ADMASYSADDR ... SDHC_ADMASYSADDR_END: + { + int off = 8 * (offset - SDHC_ADMASYSADDR); + s->admasysaddr = (s->admasysaddr & ~(0xFFFFFFFFull << off)) | + ((uint64_t)value << off); + break; + } + default: + /* Try to split 4-bytes write into two 2-byte writes */ + sdhcv2_write_2byte(s, offset, value & 0xFFFF); + sdhcv2_write_2byte(s, offset + 2, (value >> 16) & 0xFFFF); + break; + } +} + +void sdhcv2_initialize(SDHCv2State *s) +{ + DriveInfo *bd; + size_t fifo_len; + + switch (SDHC_CAPAB_BLOCKSIZE(s->capareg)) { + case 0: + fifo_len = 512; + break; + case 1: + fifo_len = 1024; + break; + case 2: + fifo_len = 2048; + break; + default: + hw_error("SDHC: unsupported value for maximum block size\n"); + break; + } + s->fifo_buffer = g_malloc0(fifo_len); + + sysbus_init_irq(&s->busdev, &s->irq); + bd = drive_get_next(IF_SD); + + if (bd) { + DPRINT_L1("SD card inserted: name = %s, sectors = %ld\n", + bd->bdrv->device_name, bd->bdrv->total_sectors); + s->card = sd_init(bd->bdrv, 0); + } else { + DPRINT_L1("No SD card\n"); + s->card = sd_init(NULL, 0); + } + s->eject_cb = qemu_allocate_irqs(sdhcv2_insert_eject_cb, s, 1)[0]; + s->ro_cb = qemu_allocate_irqs(sdhcv2_card_readonly_cb, s, 1)[0]; + sd_set_cb(s->card, s->ro_cb, s->eject_cb); + + s->insert_timer = + qemu_new_timer(vm_clock, SCALE_NS, sdhcv2_raise_insertion_irq, s); + + s->read_buffer_timer = + qemu_new_timer(vm_clock, SCALE_NS, sdhcv2_read_block_from_card, s); + + s->write_buffer_timer = + qemu_new_timer(vm_clock, SCALE_NS, sdhcv2_write_block_to_card, s); + + s->transfer_complete_timer = + qemu_new_timer(vm_clock, SCALE_NS, sdhcv2_transfer_complete_irq, s); +} + +typedef struct SDHCv2GenericState { + SDHCv2State hc; + MemoryRegion iomem; +} SDHCv2GenericState; + +static void sdhcv2_generic_reset(DeviceState *d) +{ + sdhcv2_reset(container_of(d, SDHCv2State, busdev.qdev)); +} + +static uint64_t +sdhcv2_generic_read(void *opaque, target_phys_addr_t offset, unsigned size) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + DPRINT_L2("read %u byte: offset " TARGET_FMT_plx "\n", + size, offset); + + switch (size) { + case 1: + return sdhcv2_read_1byte(s, offset); + case 2: + return sdhcv2_read_2byte(s, offset); + case 4: + return sdhcv2_read_4byte(s, offset); + } + return 0; +} + +static void +sdhcv2_generic_write(void *opaque, target_phys_addr_t offset, uint64_t val, + unsigned size) +{ + SDHCv2State *s = (SDHCv2State *)opaque; + DPRINT_L2("write %u byte: offset " TARGET_FMT_plx " = %lu(0x%lx)\n", + size, offset, val, val); + + switch (size) { + case 1: + sdhcv2_write_1byte(s, offset, val); + break; + case 2: + sdhcv2_write_2byte(s, offset, val); + break; + case 4: + sdhcv2_write_4byte(s, offset, val); + break; + } +} + +static const MemoryRegionOps sdhcv2_generic_mmio_ops = { + .read = sdhcv2_generic_read, + .write = sdhcv2_generic_write, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = false + }, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static int sdhcv2_get_fifolen(void *opaque, int version_id) +{ + SDHCv2State *sd = (SDHCv2State *)opaque; + switch (SDHC_CAPAB_BLOCKSIZE(sd->capareg)) { + case 0: + return 512; + case 1: + return 1024; + case 2: + return 2048; + default: + hw_error("SDHC: unsupported value for maximum block size\n"); + } +} + +const VMStateDescription sdhcv2_vmstate = { + .name = "sdhcv2", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(sdmasysad, SDHCv2State), + VMSTATE_UINT16(blksize, SDHCv2State), + VMSTATE_UINT16(blkcnt, SDHCv2State), + VMSTATE_UINT32(argument, SDHCv2State), + VMSTATE_UINT16(trnmod, SDHCv2State), + VMSTATE_UINT16(cmdreg, SDHCv2State), + VMSTATE_UINT32_ARRAY(rspreg, SDHCv2State, 4), + VMSTATE_UINT32(prnsts, SDHCv2State), + VMSTATE_UINT8(hostctl, SDHCv2State), + VMSTATE_UINT8(pwrcon, SDHCv2State), + VMSTATE_UINT8(blkgap, SDHCv2State), + VMSTATE_UINT8(wakcon, SDHCv2State), + VMSTATE_UINT16(clkcon, SDHCv2State), + VMSTATE_UINT8(timeoutcon, SDHCv2State), + VMSTATE_UINT8(admaerr, SDHCv2State), + VMSTATE_UINT16(norintsts, SDHCv2State), + VMSTATE_UINT16(errintsts, SDHCv2State), + VMSTATE_UINT16(norintstsen, SDHCv2State), + VMSTATE_UINT16(errintstsen, SDHCv2State), + VMSTATE_UINT16(norintsigen, SDHCv2State), + VMSTATE_UINT16(errintsigen, SDHCv2State), + VMSTATE_UINT16(acmd12errsts, SDHCv2State), + VMSTATE_UINT16(data_count, SDHCv2State), + VMSTATE_UINT16(slotint, SDHCv2State), + VMSTATE_UINT64(admasysaddr, SDHCv2State), + VMSTATE_UINT8(stoped_state, SDHCv2State), + VMSTATE_VBUFFER(fifo_buffer, SDHCv2State, 1, NULL, 0, + sdhcv2_get_fifolen), + VMSTATE_TIMER(insert_timer, SDHCv2State), + VMSTATE_TIMER(read_buffer_timer, SDHCv2State), + VMSTATE_TIMER(write_buffer_timer, SDHCv2State), + VMSTATE_TIMER(transfer_complete_timer, SDHCv2State), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription sdhcv2_generic_vmstate = { + .name = "sdhcv2_generic", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(hc, SDHCv2GenericState, 1, sdhcv2_vmstate, SDHCv2State), + VMSTATE_END_OF_LIST() + } +}; + +static int sdhcv2_generic_init(SysBusDevice *dev) +{ + SDHCv2State *s = FROM_SYSBUS(SDHCv2State, dev); + SDHCv2GenericState *hc_gen = DO_UPCAST(SDHCv2GenericState, hc, s); + + sdhcv2_initialize(s); + memory_region_init_io(&hc_gen->iomem, &sdhcv2_generic_mmio_ops, s, + "sdhcv2", SDHC_REGISTERS_MAP_SIZE); + sysbus_init_mmio(dev, &hc_gen->iomem); + + return 0; +} + +static SysBusDeviceInfo sdhcv2_generic_info = { + .init = sdhcv2_generic_init, + .qdev.name = "sdhcv2", + .qdev.size = sizeof(SDHCv2GenericState), + .qdev.vmsd = &sdhcv2_generic_vmstate, + .qdev.reset = sdhcv2_generic_reset, + .qdev.props = (Property[]) { + DEFINE_PROP_HEX32("capareg", SDHCv2GenericState, hc.capareg, + SDHC_CAPAB_REG_DEFAULT), + DEFINE_PROP_HEX32("maxcurr", SDHCv2GenericState, hc.maxcurr, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void sdhcv2_register_devices(void) +{ + sysbus_register_withprop(&sdhcv2_generic_info); +} + +device_init(sdhcv2_register_devices) diff --git a/hw/sdhc_ver2.h b/hw/sdhc_ver2.h new file mode 100644 index 0000000..2b13e16 --- /dev/null +++ b/hw/sdhc_ver2.h @@ -0,0 +1,327 @@ +/* + * SD host controller (SD Host Controller Simplified + * Specification Version 2.00 compliant) emulation + * + * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. + * All rights reserved. + * Contributed by Mitsyanko Igor <i.mitsyanko@samsung.com> + * + * Based on MMC controller for Samsung S5PC1xx-based board emulation + * by Alexey Merkulov and Vladimir Monakhov. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SDHC_VER2_H_ +#define SDHC_VER2_H_ + +#include "qemu-common.h" +#include "sysbus.h" +#include "sd.h" + +/* R/W SDMA System Address register 0x0 */ +#define SDHC_SYSAD 0x00 +#define SDHC_SYSAD_END 0x03 + +/* R/W Host DMA Buffer Boundary and Transfer Block Size Register 0x0 */ +#define SDHC_BLKSIZE 0x04 +#define SDHC_BLKSIZE_END 0x05 + +/* R/W Blocks count for current transfer 0x0 */ +#define SDHC_BLKCNT 0x06 +#define SDHC_BLKCNT_END 0x07 + +/* R/W Command Argument Register 0x0 */ +#define SDHC_ARGUMENT 0x08 +#define SDHC_ARGUMENT_END 0x0B + +/* R/W Transfer Mode Setting Register 0x0 */ +#define SDHC_TRNMOD 0x0C +#define SDHC_TRNMOD_END 0x0D +#define SDHC_TRNS_DMA 0x0001 +#define SDHC_TRNS_BLK_CNT_EN 0x0002 +#define SDHC_TRNS_ACMD12 0x0004 +#define SDHC_TRNS_READ 0x0010 +#define SDHC_TRNS_MULTI 0x0020 + +/* R/W Command Register 0x0 */ +#define SDHC_CMDREG 0x0E +#define SDHC_CMDREG_END 0x0F +#define SDHC_CMD_RSP_WITH_BUSY (3 << 0) +#define SDHC_CMD_DATA_PRESENT (1 << 5) +#define SDHC_CMD_SUSPEND (1 << 6) +#define SDHC_CMD_RESUME (1 << 7) +#define SDHC_CMD_ABORT ((1 << 6)|(1 << 7)) +#define SDHC_CMD_TYPE_MASK ((1 << 6)|(1 << 7)) +#define SDHC_COMMAND_TYPE(x) ((x) & SDHC_CMD_TYPE_MASK) + +/* ROC Response Register 0 0x0 */ +#define SDHC_RSPREG0 0x10 +/* ROC Response Register 1 0x0 */ +#define SDHC_RSPREG1 0x14 +/* ROC Response Register 2 0x0 */ +#define SDHC_RSPREG2 0x18 +/* ROC Response Register 3 0x0 */ +#define SDHC_RSPREG3 0x1C +#define SDHC_RSPREG3_END 0x1F + +/* R/W Buffer Data Register 0x0 */ +#define SDHC_BDATA 0x20 +#define SDHC_BDATA_END 0x23 +#define SDHC_BUFFER_END (-1) + +/* R/ROC Present State Register 0x000A0000 */ +#define SDHC_PRNSTS 0x24 +#define SDHC_PRNSTS_END 0x27 +#define SDHC_CMD_INHIBIT 0x00000001 +#define SDHC_DATA_INHIBIT 0x00000002 +#define SDHC_DAT_LINE_ACTIVE 0x00000004 +#define SDHC_DOING_WRITE 0x00000100 +#define SDHC_DOING_READ 0x00000200 +#define SDHC_SPACE_AVAILABLE 0x00000400 +#define SDHC_DATA_AVAILABLE 0x00000800 +#define SDHC_CARD_PRESENT 0x00010000 +#define SDHC_WRITE_PROTECT 0x00080000 +#define TRANSFERRING_DATA(x) \ + ((x) & (SDHC_DOING_READ | SDHC_DOING_WRITE)) + +/* R/W Host control Register 0x0 */ +#define SDHC_HOSTCTL 0x28 +#define SDHC_CTRL_DMA_CHECK_MASK 0x18 +#define SDHC_CTRL_SDMA 0x00 +#define SDHC_CTRL_ADMA_32 0x10 +#define SDHC_CTRL_ADMA_64 0x18 +#define SDHC_DMA_TYPE(x) ((x) & SDHC_CTRL_DMA_CHECK_MASK) + +/* R/W Power Control Register 0x0 */ +#define SDHC_PWRCON 0x29 +#define SDHC_POWER_ON (1 << 0) + +/* R/W Block Gap Control Register 0x0 */ +#define SDHC_BLKGAP 0x2A +#define SDHC_STOP_AT_GAP_REQ 0x01 +#define SDHC_CONTINUE_REQ 0x02 + +/* R/W WakeUp Control Register 0x0 */ +#define SDHC_WAKCON 0x2B +#define SDHC_WKUP_ON_INSERT (1 << 1) +#define SDHC_WKUP_ON_REMOVE (1 << 2) + +/* CLKCON */ +#define SDHC_CLKCON 0x2C +#define SDHC_CLKCON_END 0x2D +#define SDHC_CLOCK_INT_STABLE 0x0002 +#define SDHC_CLOCK_INT_EN 0x0001 +#define SDHC_CLOCK_SDCLK_EN (1 << 2) +#define SDHC_CLOCK_CHK_MASK 0x0007 +#define SDHC_CLOCK_IS_ON(x) \ + (((x) & SDHC_CLOCK_CHK_MASK) == SDHC_CLOCK_CHK_MASK) + +/* R/W Timeout Control Register 0x0 */ +#define SDHC_TIMEOUTCON 0x2E + +/* R/W Software Reset Register 0x0 */ +#define SDHC_SWRST 0x2F +#define SDHC_RESET_ALL 0x01 +#define SDHC_RESET_CMD 0x02 +#define SDHC_RESET_DATA 0x04 + +/* ROC/RW1C Normal Interrupt Status Register 0x0 */ +#define SDHC_NORINTSTS 0x30 +#define SDHC_NORINTSTS_END 0x31 +#define SDHC_NIS_ERR 0x8000 +#define SDHC_NIS_CMDCMP 0x0001 +#define SDHC_NIS_TRSCMP 0x0002 +#define SDHC_NIS_BLKGAP 0x0004 +#define SDHC_NIS_DMA 0x0008 +#define SDHC_NIS_WBUFRDY 0x0010 +#define SDHC_NIS_RBUFRDY 0x0020 +#define SDHC_NIS_INSERT 0x0040 +#define SDHC_NIS_REMOVE 0x0080 +#define SDHC_NIS_CARDINT 0x0100 + +/* ROC/RW1C Error Interrupt Status Register 0x0 */ +#define SDHC_ERRINTSTS 0x32 +#define SDHC_ERRINTSTS_END 0x33 +#define SDHC_EIS_CMDTIMEOUT 0x0001 +#define SDHC_EIS_BLKGAP 0x0004 +#define SDHC_EIS_CMD12ERR 0x0100 +#define SDHC_EIS_ADMAERR 0x0200 + +/* R/W Normal Interrupt Status Enable Register 0x0 */ +#define SDHC_NORINTSTSEN 0x34 +#define SDHC_NORINTSTSEN_END 0x35 +#define SDHC_NISEN_CMDCMP 0x0001 +#define SDHC_NISEN_TRSCMP 0x0002 +#define SDHC_NISEN_DMA 0x0008 +#define SDHC_NISEN_WBUFRDY 0x0010 +#define SDHC_NISEN_RBUFRDY 0x0020 +#define SDHC_NISEN_INSERT 0x0040 +#define SDHC_NISEN_REMOVE 0x0080 +#define SDHC_NISEN_CARDINT 0x0100 + +/* R/W Error Interrupt Status Enable Register 0x0 */ +#define SDHC_ERRINTSTSEN 0x36 +#define SDHC_ERRINTSTSEN_END 0x37 +#define SDHC_EISEN_CMDTIMEOUT 0x0001 +#define SDHC_EISEN_BLKGAP 0x0004 +#define SDHC_EISEN_ADMAERR 0x0200 + +/* R/W Normal Interrupt Signal Enable Register 0x0 */ +#define SDHC_NORINTSIGEN 0x38 +#define SDHC_NORINTSIGEN_END 0x39 +#define SDHC_NORINTSIG_INSERT (1 << 6) +#define SDHC_NORINTSIG_REMOVE (1 << 7) + +/* R/W Error Interrupt Signal Enable Register 0x0 */ +#define SDHC_ERRINTSIGEN 0x3A +#define SDHC_ERRINTSIGEN_END 0x3B + +/* ROC Auto CMD12 error status register 0x0 */ +#define SDHC_ACMD12ERRSTS 0x3C +#define SDHC_ACMD12ERRSTS_END 0x3D + +/* HWInit Capabilities Register 0x05E80080 */ +#define SDHC_CAPAREG 0x40 +#define SDHC_CAPAREG_END 0x43 +#define SDHC_CAN_DO_DMA 0x00400000 +#define SDHC_CAN_DO_ADMA 0x00080000 +#define SDHC_64_BIT_BUS_SUPPORT (1 << 28) +#define SDHC_CAPAB_BLOCKSIZE(x) (((x) >> 16) & 0x3) + +/* HWInit Maximum Current Capabilities Register 0x0 */ +#define SDHC_MAXCURR 0x48 +#define SDHC_MAXCURR_END 0x4B + +/* W Force Event Auto CMD12 Error Interrupt Register 0x0000 */ +#define SDHC_FEAER 0x50 +#define SDHC_FEAER_END 0x51 +/* W Force Event Error Interrupt Register Error Interrupt 0x0000 */ +#define SDHC_FEERR 0x52 +#define SDHC_FEERR_END 0x53 + +/* R/W ADMA Error Status Register 0x00 */ +#define SDHC_ADMAERR 0x54 +#define SDHC_ADMAERR_LENGTH_MISMATCH (1 << 2) +#define SDHC_ADMAERR_STATE_ST_STOP (0 << 0) +#define SDHC_ADMAERR_STATE_ST_FDS (1 << 0) +#define SDHC_ADMAERR_STATE_ST_TFR (3 << 0) +#define SDHC_ADMAERR_STATE_MASK (3 << 0) + +/* R/W ADMA System Address Register 0x00 */ +#define SDHC_ADMASYSADDR 0x58 +#define SDHC_ADMASYSADDR_END 0x5F +#define SDHC_ADMA_ATTR_ACT_TRAN (1 << 5) +#define SDHC_ADMA_ATTR_ACT_LINK (3 << 4) +#define SDHC_ADMA_ATTR_INT (1 << 2) +#define SDHC_ADMA_ATTR_END (1 << 1) +#define SDHC_ADMA_ATTR_VALID (1 << 0) +#define SDHC_ADMA_ATTR_ACT_MASK ((1 << 4)|(1 << 5)) + +/* Slot interrupt status */ +#define SDHC_SLOT_INT_STATUS 0xFC +#define SDHC_SLOT_INT_STATUS_END 0xFD + +/* HWInit Host Controller Version Register 0x0401 */ +#define SDHC_HCVER 0xFE +#define SDHC_HCVER_END 0xFF +#define SD_HOST_SPECv2_VERS 0x2401 + +#define SDHC_REGISTERS_MAP_SIZE 0x100 +#define SDHC_READ_BUFFER_DELAY 1 +#define SDHC_WRITE_BUFFER_DELAY 2 +#define SDHC_INSERTION_DELAY (get_ticks_per_sec()) +#define SDHC_CMD_RESPONSE (3 << 0) + +enum { + sdhc_not_stoped = 0, /* normal SDHC state */ + sdhc_gap_read = 1, /* SDHC stopped at block gap during read operation */ + sdhc_gap_write = 2 /* SDHC stopped at block gap during write operation */ +}; + +/* SD/MMC host controller state */ +typedef struct SDHCv2State { + SysBusDevice busdev; + SDState *card; + + QEMUTimer *insert_timer; /* timer for 'changing' sd card. */ + QEMUTimer *read_buffer_timer; /* read block of data to controller FIFO */ + QEMUTimer *write_buffer_timer; /* write block of data to card from FIFO */ + QEMUTimer *transfer_complete_timer; /* raise transfer complete irq */ + qemu_irq eject_cb; + qemu_irq ro_cb; + qemu_irq irq; + + uint32_t sdmasysad; /* SDMA System Address register */ + uint16_t blksize; /* Host DMA Buff Boundary and Transfer BlkSize Reg */ + uint16_t blkcnt; /* Blocks count for current transfer */ + uint32_t argument; /* Command Argument Register */ + uint16_t trnmod; /* Transfer Mode Setting Register */ + uint16_t cmdreg; /* Command Register */ + uint32_t rspreg[4]; /* Response Registers 0-3 */ + uint32_t prnsts; /* Present State Register */ + uint8_t hostctl; /* Present State Register */ + uint8_t pwrcon; /* Present State Register */ + uint8_t blkgap; /* Block Gap Control Register */ + uint8_t wakcon; /* WakeUp Control Register */ + uint16_t clkcon; /* Command Register */ + uint8_t timeoutcon; /* Timeout Control Register */ + uint8_t admaerr; /* ADMA Error Status Register */ + uint16_t norintsts; /* Normal Interrupt Status Register */ + uint16_t errintsts; /* Error Interrupt Status Register */ + uint16_t norintstsen; /* Normal Interrupt Status Enable Register */ + uint16_t errintstsen; /* Error Interrupt Status Enable Register */ + uint16_t norintsigen; /* Normal Interrupt Signal Enable Register */ + uint16_t errintsigen; /* Error Interrupt Signal Enable Register */ + uint16_t acmd12errsts; /* Auto CMD12 error status register */ + uint16_t slotint; /* Slot interrupt status register */ + uint64_t admasysaddr; /* ADMA System Address Register */ + + uint32_t capareg; /* Capabilities Register */ + uint32_t maxcurr; /* Maximum Current Capabilities Register */ + uint8_t *fifo_buffer; /* SD host i/o FIFO buffer */ + uint16_t data_count; /* current element in FIFO buffer */ + uint8_t stoped_state; /* Current SDHC state */ + /* Buffer Data Port Register - virtual access point to R and W buffers */ + /* Software Reset Register - always reads as 0 */ + /* Force Event Auto CMD12 Error Interrupt Reg - write only */ + /* Force Event Error Interrupt Register- write only */ + /* RO Host Controller Version Register always reads as 0x2401 */ +} SDHCv2State; + +extern const VMStateDescription sdhcv2_vmstate; + +void sdhcv2_initialize(SDHCv2State *s); +void sdhcv2_reset(SDHCv2State *s); +uint8_t sdhcv2_read_1byte(SDHCv2State *s, target_phys_addr_t offset); +uint16_t sdhcv2_read_2byte(SDHCv2State *s, target_phys_addr_t offset); +uint32_t sdhcv2_read_4byte(SDHCv2State *s, target_phys_addr_t offset); +void sdhcv2_write_1byte(SDHCv2State *s, target_phys_addr_t offset, + uint32_t value); +void sdhcv2_write_2byte(SDHCv2State *s, target_phys_addr_t offset, + uint16_t value); +void sdhcv2_write_4byte(SDHCv2State *s, target_phys_addr_t offset, + uint32_t value); +void sdhcv2_send_command(SDHCv2State *s); +void sdhcv2_raise_response_recieved_irq(SDHCv2State *s); +void sdhcv2_trigger_command_generation(SDHCv2State *s); +void sdhcv2_transfer_data(SDHCv2State *s); +void sdhcv2_sdma_transfer_single_block(SDHCv2State *s); +void sdhcv2_sdma_transfer_multi_blocks(SDHCv2State *s); +void sdhcv2_start_adma(SDHCv2State *s); +void sdhcv2_do_transfer_complete(SDHCv2State *s); +uint32_t sdhcv2_read_dataport(SDHCv2State *s, unsigned size); +void sdhcv2_write_dataport(SDHCv2State *s, uint32_t value, unsigned size); + +#endif /* SDHC_VER2_H_ */ -- 1.7.4.1 ^ permalink raw reply related [flat|nested] 31+ messages in thread
end of thread, other threads:[~2012-01-19 13:42 UTC | newest] Thread overview: 31+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2011-12-26 10:03 [Qemu-devel] [PATCH 0/3] Improve SD controllers emulation Mitsyanko Igor 2011-12-26 10:03 ` [Qemu-devel] [PATCH 1/3] vmstate: introduce calc_size VMStateField Mitsyanko Igor 2011-12-26 15:20 ` Peter Maydell 2011-12-27 8:11 ` Mitsyanko Igor 2011-12-27 13:10 ` Andreas Färber 2011-12-28 7:41 ` Mitsyanko Igor 2011-12-27 7:54 ` [Qemu-devel] [PATCH V2 1/3] vmstate: introduce get_bufsize entry in VMStateField Mitsyanko Igor 2011-12-26 10:03 ` [Qemu-devel] [PATCH 2/3] hw/sd.c: add SD card save/load support Mitsyanko Igor 2011-12-26 14:58 ` Peter Maydell 2011-12-27 11:27 ` Mitsyanko Igor 2011-12-27 13:27 ` Andreas Färber 2011-12-27 13:54 ` Mitsyanko Igor 2011-12-27 14:13 ` Avi Kivity 2011-12-27 21:30 ` Peter Maydell 2011-12-28 9:50 ` Avi Kivity 2011-12-26 10:03 ` [Qemu-devel] [PATCH 3/3] hw/: Introduce spec. ver. 2.00 compliant SD host controller Mitsyanko Igor 2011-12-26 11:35 ` malc 2011-12-28 12:08 ` [Qemu-devel] [PATCH V2 0/3] Improve SD controllers emulation Mitsyanko Igor 2011-12-28 12:08 ` [Qemu-devel] [PATCH V2 1/3] vmstate: introduce get_bufsize entry in VMStateField Mitsyanko Igor 2011-12-28 12:08 ` [Qemu-devel] [PATCH V2 2/3] hw/sd.c: add SD card save/load support Mitsyanko Igor 2011-12-28 13:26 ` Peter Maydell 2011-12-28 14:02 ` Mitsyanko Igor 2011-12-28 14:41 ` Peter Maydell 2011-12-28 12:08 ` [Qemu-devel] [PATCH V2 3/3] hw: Introduce spec. ver. 2.00 compliant SD host controller Mitsyanko Igor 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 0/5] Improve SD controllers emulation Mitsyanko Igor 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 1/5] vmstate: introduce get_bufsize entry in VMStateField Mitsyanko Igor 2012-01-19 13:40 ` Andreas Färber 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 2/5] hw/sd.c: add SD card save/load support Mitsyanko Igor 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 3/5] hw/sd.c: convert wp_groups, expecting_acmd and enable to bool Mitsyanko Igor 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 4/5] hw/sd.c: convert wp_switch and spi " Mitsyanko Igor 2011-12-28 15:32 ` [Qemu-devel] [PATCH V3 5/5] hw: Introduce spec. ver. 2.00 compliant SD host controller Mitsyanko Igor
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).