qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-devel] [RFC 02/10] AHCI: Update byte count after DMA completio


From: Paolo Bonzini
Subject: Re: [Qemu-devel] [RFC 02/10] AHCI: Update byte count after DMA completion
Date: Sat, 13 Sep 2014 15:21:22 +0200
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Thunderbird/31.0

Il 13/09/2014 06:34, John Snow ha scritto:
> Currently, DMA read/write operations neglect to update
> the byte count after a successful transfer like ATAPI
> DMA read or PIO read/write operations do.
> 
> We correct this oversight by adding another callback into
> the IDEDMAOps structure. The commit callback is called
> whenever we are cleaning up a scatter-gather list.
> AHCI can register this callback in order to update post-
> transfer information such as byte count updates.
> 
> We use this callback in AHCI to consolidate where we delete
> the SGlist as generated from the PRDT, as well as update the
> byte count after the transfer is complete.
> 
> The QEMUSGList structure has an init flag added to it in order
> to make qemu_sglist_destroy a nop if it is called when
> there is no sglist, which simplifies cleanup and error paths.
> 
> This patch fixes several AHCI problems, notably Non-NCQ modes
> of operation for Windows 7 as well as Hibernate support for Windows 7.

I think this patch is touching the internals more than it should.
There's obviously some good stuff here, but the abstractions needed to
fix the problem are already there.

> Signed-off-by: John Snow <address@hidden>
> ---
>  dma-helpers.c        |  5 +++++
>  hw/ide/ahci.c        | 47 +++++++++++++++++++++++++++++++++++++----------
>  hw/ide/core.c        | 14 +++++++++-----
>  hw/ide/internal.h    |  1 +
>  include/sysemu/dma.h |  1 +
>  5 files changed, 53 insertions(+), 15 deletions(-)
> 
> diff --git a/dma-helpers.c b/dma-helpers.c
> index 499b52b..ba965a3 100644
> --- a/dma-helpers.c
> +++ b/dma-helpers.c
> @@ -45,6 +45,7 @@ void qemu_sglist_init(QEMUSGList *qsg, DeviceState *dev, 
> int alloc_hint,
>      qsg->as = as;
>      qsg->dev = dev;
>      object_ref(OBJECT(dev));
> +    qsg->init = true;
>  }
>  
>  void qemu_sglist_add(QEMUSGList *qsg, dma_addr_t base, dma_addr_t len)
> @@ -61,6 +62,10 @@ void qemu_sglist_add(QEMUSGList *qsg, dma_addr_t base, 
> dma_addr_t len)
>  
>  void qemu_sglist_destroy(QEMUSGList *qsg)
>  {
> +    if (!qsg->init) {
> +        return;
> +    }
> +
>      object_unref(OBJECT(qsg->dev));
>      g_free(qsg->sg);
>      memset(qsg, 0, sizeof(*qsg));

Why do you need this?  qemu_sglist_destroy is idempotent (neither free
nor unref of NULL cause problems).

> diff --git a/hw/ide/ahci.c b/hw/ide/ahci.c
> index 0ee713b..d3ece78 100644
> --- a/hw/ide/ahci.c
> +++ b/hw/ide/ahci.c
> @@ -48,6 +48,9 @@ static int handle_cmd(AHCIState *s,int port,int slot);
>  static void ahci_reset_port(AHCIState *s, int port);
>  static void ahci_write_fis_d2h(AHCIDevice *ad, uint8_t *cmd_fis);
>  static void ahci_init_d2h(AHCIDevice *ad);
> +static int ahci_dma_prepare_buf(IDEDMA *dma, int is_write);
> +static int ahci_commit_buf(IDEDMA *dma, int xfer_ok);
> +
>  
>  static uint32_t  ahci_port_read(AHCIState *s, int port, int offset)
>  {
> @@ -1084,16 +1087,12 @@ static void ahci_start_transfer(IDEDMA *dma)
>          }
>      }
>  
> -    /* update number of transferred bytes */
> -    ad->cur_cmd->status = cpu_to_le32(le32_to_cpu(ad->cur_cmd->status) + 
> size);
> -
>  out:
>      /* declare that we processed everything */
>      s->data_ptr = s->data_end;
>  
> -    if (has_sglist) {
> -        qemu_sglist_destroy(&s->sg);
> -    }
> +    /* Update number of transferred bytes, destroy sglist */
> +    ahci_commit_buf(dma, true);
>  
>      s->end_transfer_func(s);
>  
> @@ -1114,6 +1113,11 @@ static void ahci_start_dma(IDEDMA *dma, IDEState *s,
>      dma_cb(s, 0);
>  }
>  
> +/**
> + * Called in DMA R/W chains to read the PRDTL, utilizing 
> ahci_populate_sglist.
> + * Not currently invoked by PIO R/W chains,
> + * which invoke ahci_populate_sglist via ahci_start_transfer.
> + */
>  static int ahci_dma_prepare_buf(IDEDMA *dma, int is_write)
>  {
>      AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
> @@ -1126,6 +1130,30 @@ static int ahci_dma_prepare_buf(IDEDMA *dma, int 
> is_write)
>      return s->io_buffer_size != 0;
>  }
>  
> +/**
> + * Destroys the scatter-gather list,
> + * and updates the command header with a bytes-read value.
> + * called explicitly via ahci_dma_rw_buf (ATAPI DMA),
> + * and ahci_start_transfer (PIO R/W),
> + * and called via callback from ide_dma_cb for DMA R/W paths.
> + */
> +static int ahci_commit_buf(IDEDMA *dma, int xfer_ok)
> +{
> +    AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
> +    IDEState *s = &ad->port.ifs[0];
> +    uint32_t byte_count;
> +
> +    if (xfer_ok) {
> +        byte_count = le32_to_cpu(ad->cur_cmd->status);
> +        byte_count += s->sg.size;
> +        ad->cur_cmd->status = cpu_to_le32(byte_count);
> +    }

If you leave qemu_sglist_destroy in the caller, you do not need to call
ahci_commit_buf in the error cases.  Maybe I'm wrong, but I have a
feeling that it would be much simpler to me if your commit hook is simply

       byte_count += le32_to_cpu(ad->cur_cmd->status);
       ad->cur_cmd->status = cpu_to_le32(byte_count);

where byte_count is the second argument to the hook.  The callers can
pass 0 in the error case, s->sg.size otherwise.

> +    qemu_sglist_destroy(&s->sg);
> +
> +    return s->sg.size != 0;

Also, there's no reason to keep the return value (which is always false).

> +}
> +
>  static int ahci_dma_rw_buf(IDEDMA *dma, int is_write)
>  {
>      AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma);
> @@ -1143,11 +1171,9 @@ static int ahci_dma_rw_buf(IDEDMA *dma, int is_write)
>          dma_buf_write(p, l, &s->sg);
>      }
>  
> -    /* free sglist that was created in ahci_populate_sglist() */
> -    qemu_sglist_destroy(&s->sg);
> +    /* free sglist, update byte count */
> +    ahci_commit_buf(dma, true);

Perhaps you should make dma_buf_commit public (and add the call to the
hook), so that ahci_commit_buf does not have to mess with &s->sg.

> -    /* update number of transferred bytes */
> -    ad->cur_cmd->status = cpu_to_le32(le32_to_cpu(ad->cur_cmd->status) + l);
>      s->io_buffer_index += l;
>      s->io_buffer_offset += l;
>  
> @@ -1190,6 +1216,7 @@ static const IDEDMAOps ahci_dma_ops = {
>      .start_dma = ahci_start_dma,
>      .start_transfer = ahci_start_transfer,
>      .prepare_buf = ahci_dma_prepare_buf,
> +    .commit_buf = ahci_commit_buf,
>      .rw_buf = ahci_dma_rw_buf,
>      .set_unit = ahci_dma_set_unit,
>      .cmd_done = ahci_cmd_done,
> diff --git a/hw/ide/core.c b/hw/ide/core.c
> index 3d682e2..b2980e9 100644
> --- a/hw/ide/core.c
> +++ b/hw/ide/core.c
> @@ -629,9 +629,13 @@ void ide_sector_read(IDEState *s)
>                                    ide_sector_read_cb, s);
>  }
>  
> -static void dma_buf_commit(IDEState *s)
> +static void dma_buf_commit(IDEState *s, int xfer_ok)
>  {
> -    qemu_sglist_destroy(&s->sg);
> +    if (s->bus->dma->ops->commit_buf) {
> +        s->bus->dma->ops->commit_buf(s->bus->dma, xfer_ok);
> +    } else {

The "else" is not necessary, I think.

> +        qemu_sglist_destroy(&s->sg);
> +    }
>  }
>  
>  void ide_set_inactive(IDEState *s, bool more)
> @@ -660,7 +664,7 @@ static int ide_handle_rw_error(IDEState *s, int error, 
> int op)
>          s->bus->error_status = op;
>      } else if (action == BLOCK_ERROR_ACTION_REPORT) {
>          if (op & IDE_RETRY_DMA) {
> -            dma_buf_commit(s);
> +            dma_buf_commit(s, false);

For the two error cases, it makes sense to move the call to
dma_buf_commit from the callers to ide_dma_error.

>              ide_dma_error(s);
>          } else {
>              ide_rw_error(s);
> @@ -701,7 +705,7 @@ void ide_dma_cb(void *opaque, int ret)
>  
>      sector_num = ide_get_sector(s);
>      if (n > 0) {
> -        dma_buf_commit(s);
> +        dma_buf_commit(s, true);
>          sector_num += n;
>          ide_set_sector(s, sector_num);
>          s->nsector -= n;
> @@ -732,7 +736,7 @@ void ide_dma_cb(void *opaque, int ret)
>  
>      if ((s->dma_cmd == IDE_DMA_READ || s->dma_cmd == IDE_DMA_WRITE) &&
>          !ide_sect_range_ok(s, sector_num, n)) {
> -        dma_buf_commit(s);
> +        dma_buf_commit(s, false);
>          ide_dma_error(s);
>          return;
>      }
> diff --git a/hw/ide/internal.h b/hw/ide/internal.h
> index 72d0147..f190e8c 100644
> --- a/hw/ide/internal.h
> +++ b/hw/ide/internal.h
> @@ -432,6 +432,7 @@ struct IDEDMAOps {
>      DMAStartFunc *start_dma;
>      DMAVoidFunc *start_transfer;
>      DMAIntFunc *prepare_buf;
> +    DMAIntFunc *commit_buf;
>      DMAIntFunc *rw_buf;
>      DMAIntFunc *set_unit;
>      DMAStopFunc *set_inactive;
> diff --git a/include/sysemu/dma.h b/include/sysemu/dma.h
> index 00f21f3..8d2c4d6 100644
> --- a/include/sysemu/dma.h
> +++ b/include/sysemu/dma.h
> @@ -31,6 +31,7 @@ struct QEMUSGList {
>      size_t size;
>      DeviceState *dev;
>      AddressSpace *as;
> +    bool init;
>  };
>  
>  #ifndef CONFIG_USER_ONLY
> 





reply via email to

[Prev in Thread] Current Thread [Next in Thread]