On Thu, Jan 29, 2015 at 02:24:09PM +0000, Simon Farnsworth wrote:
> On Thursday 29 January 2015 15:30:36 you wrote:
> > On Tue, Jan 27, 2015 at 03:43:49PM +0000, Simon Farnsworth wrote:
> --snip--
> > > diff --git a/drivers/gpu/drm/drm_dp_helper.c 
> > > b/drivers/gpu/drm/drm_dp_helper.c
> > > index 79968e3..3db116c 100644
> > > --- a/drivers/gpu/drm/drm_dp_helper.c
> > > +++ b/drivers/gpu/drm/drm_dp_helper.c
> > > @@ -396,11 +396,13 @@ static u32 drm_dp_i2c_functionality(struct 
> > > i2c_adapter *adapter)
> > >   * retrying the transaction as appropriate.  It is assumed that the
> > >   * aux->transfer function does not modify anything in the msg other than 
> > > the
> > >   * reply field.
> > > + *
> > > + * Returns bytes transferred on success, or a negative error code on 
> > > failure.
> > >   */
> > >  static int drm_dp_i2c_do_msg(struct drm_dp_aux *aux, struct 
> > > drm_dp_aux_msg *msg)
> > >  {
> > >   unsigned int retry;
> > > - int err;
> > > + int ret;
> > >  
> > >   /*
> > >    * DP1.2 sections 2.7.7.1.5.6.1 and 2.7.7.1.6.6.1: A DP Source device
> > > @@ -409,14 +411,14 @@ static int drm_dp_i2c_do_msg(struct drm_dp_aux 
> > > *aux, struct drm_dp_aux_msg *msg)
> > >    */
> > >   for (retry = 0; retry < 7; retry++) {
> > >           mutex_lock(&aux->hw_mutex);
> > > -         err = aux->transfer(aux, msg);
> > > +         ret = aux->transfer(aux, msg);
> > >           mutex_unlock(&aux->hw_mutex);
> > > -         if (err < 0) {
> > > -                 if (err == -EBUSY)
> > > +         if (ret < 0) {
> > > +                 if (ret == -EBUSY)
> > >                           continue;
> > >  
> > > -                 DRM_DEBUG_KMS("transaction failed: %d\n", err);
> > > -                 return err;
> > > +                 DRM_DEBUG_KMS("transaction failed: %d\n", ret);
> > > +                 return ret;
> > >           }
> > >  
> > >  
> > > @@ -457,9 +459,7 @@ static int drm_dp_i2c_do_msg(struct drm_dp_aux *aux, 
> > > struct drm_dp_aux_msg *msg)
> > >                    * Both native ACK and I2C ACK replies received. We
> > >                    * can assume the transfer was successful.
> > >                    */
> > > -                 if (err < msg->size)
> > > -                         return -EPROTO;
> > > -                 return 0;
> > > +                 return ret;
> > 
> > The s/err/ret/ seems a bit superfluous, but OTOH it does make sense
> > since it's no longer just an error value. At first glance it just
> > confused me a bit since I wasn't expecting that many changes to this
> > function.
> >
> 
> I'm going to hang onto that change - it makes things clearer when you read
> the function outside the context of this patch.
> 
> > >  
> > >           case DP_AUX_I2C_REPLY_NACK:
> > >                   DRM_DEBUG_KMS("I2C nack\n");
> > > @@ -482,14 +482,67 @@ static int drm_dp_i2c_do_msg(struct drm_dp_aux 
> > > *aux, struct drm_dp_aux_msg *msg)
> > >   return -EREMOTEIO;
> > >  }
> > >  
> > > +/*
> > > + * Keep retrying drm_dp_i2c_do_msg until all data has been transferred.
> > > + *
> > > + * Returns an error code on failure, or a recommended transfer size on 
> > > success.
> > > + */
> > > +static int drm_dp_i2c_drain_msg(struct drm_dp_aux *aux, struct 
> > > drm_dp_aux_msg *msg)
> > > +{
> > > + int ret;
> > > + struct drm_dp_aux_msg drain_msg;
> > > + int drain_bytes;
> > > +
> > > + ret = drm_dp_i2c_do_msg(aux, msg);
> > > +
> > > + if (ret == msg->size || ret <= 0)
> > > +         return ret == 0 ? -EPROTO : ret;
> > > +
> > > + /*
> > > +  * We had a partial reply. Drain out the rest of the bytes we
> > > +  * originally requested, then return the size of the partial
> > > +  * reply. In theory, this should match DP 1.2's desired behaviour
> > > +  * for I2C over AUX.
> > > +  */
> > 
> > The spec is a bit self contradictory, but there is a section which seems
> > to suggest this. It's only mentioned for reads mind you, but I don't see
> > it causing harm for writes either. Not that we actually handle
> > partial/deferred writes correctly at the moment.
> > 
> > > + DRM_DEBUG_KMS("Partial I2C reply: requested %zu bytes got %d bytes\n", 
> > > msg->size, ret);
> > > + drain_msg = *msg;
> > > + drain_msg.size -= ret;
> > > + drain_msg.buffer += ret;
> > > + while (drain_msg.size != 0) {
> > > +         drain_bytes = drm_dp_i2c_do_msg(aux, &drain_msg);
> > > +         if (drain_bytes <= 0)
> > > +                 return drain_bytes == 0 ? -EPROTO : drain_bytes;
> > > +         drain_msg.size -= drain_bytes;
> > > +         drain_msg.buffer += drain_bytes;
> > > + }
> > 
> > Somehow I don't like the duplicated code end up having here. So
> > putting it all in a single loop would seem nicer to me. Maybe
> > something along these lines?
> > 
> > struct drm_dp_aux_msg msg = *orig_msg;
> > int ret = msg.size;
> > while (msg.size > 0) {
> >     int err = drm_dp_i2c_do_msg(aux, &msg);
> >     if (err <= 0)
> >             return err == 0 ? -EPROTO : err;
> > 
> >     if (err < msg.size && err < ret) {
> >             DRM_DEBUG_KMS("Partial I2C reply: requested %zu
> >                            bytes got %d bytes\n", msg.size, err);
> >             ret = err;
> >     }
> > 
> >     msg.size -= err;
> >     msg.buffer += err;
> > }
> > 
> > It would also reduce the returned preferred transfer size further if we
> > (for whatever reason) got an even shorter reply while we're draining.
> >
> I'm not sure that that's the right behaviour, though. If we assume a 3 byte
> FIFO in a device that does partial reads, we ask for 16 bytes, causing a
> partial response that's 3 bytes long. We then drain out the remaining 13
> bytes of the initial request (in case it's set up a 16 byte I2C transfer),
> and the last of the reads is guaranteed to be 1 byte long.

My proposed code wouldn't reduce the transfer size in that case due to
the err<msg.size check. So it only considers shrinking the transfer size
when the reply came back with less data than was requested.

> 
> We then shrink to 1 byte transfers, when the device would be capable of 3
> byte transfers, and could possibly perform better with 3 byte transfers
> rather than 1.
> 
> Having said that, this is all hypothetical until we find devices that do
> partial replies - no-one's been able to find such a device so far.
> 
> I'll have a think and see if I can come up with a way to get the behaviour I
> want with less code duplication - I might be able to do something by using a
> sentinel value to spot first time round the loop.
> 
> > 
> > > + return ret;
> > > +}
> > > +
> > > +/*
> > > + * Bizlink designed DP->DVI-D Dual Link adapters require the I2C over AUX
> > > + * packets to be as large as possible. If not, the I2C transactions never
> > > + * succeed. Hence the default is maximum.
> > > + */
> > > +static int dp_aux_i2c_transfer_size __read_mostly = 
> > > DP_AUX_MAX_PAYLOAD_BYTES;
> > > +module_param_named(dp_aux_i2c_transfer_size, dp_aux_i2c_transfer_size, 
> > > int, 0600);
> > > +MODULE_PARM_DESC(dp_aux_i2c_transfer_size,
> > > +          "Number of bytes to transfer in a single I2C over DP AUX CH 
> > > message, (1-16, default 16)");
> > > +
> > >  static int drm_dp_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg 
> > > *msgs,
> > >                      int num)
> > >  {
> > >   struct drm_dp_aux *aux = adapter->algo_data;
> > >   unsigned int i, j;
> > > + int transfer_size;
> > >   struct drm_dp_aux_msg msg;
> > >   int err = 0;
> > >  
> > > + if (dp_aux_i2c_transfer_size < 1 || dp_aux_i2c_transfer_size > 
> > > DP_AUX_MAX_PAYLOAD_BYTES) {
> > > +         DRM_ERROR("dp_aux_i2c_transfer_size invalid - changing from %d 
> > > to %d\n",
> > > +                   dp_aux_i2c_transfer_size, DP_AUX_MAX_PAYLOAD_BYTES);
> > > +         dp_aux_i2c_transfer_size = DP_AUX_MAX_PAYLOAD_BYTES;
> > > + }
> > 
> > The invalid values could be rejected with custom struct
> > kernel_param_ops, but maybe that's a bit overkill. If not that, then
> > I'm not sure the error message really has that much value. So I'm thinking
> > we could just 'clamp(..., 1, DP_AUX_MAX_PAYLOAD_BYTES)' here.
> >
> I'll remove the message for v4, as well as marking the parameter unsafe with
> module_param_named_unsafe, and use clamp instead of if().
> 
> > > +
> > >   memset(&msg, 0, sizeof(msg));
> > >  
> > >   for (i = 0; i < num; i++) {
> > > @@ -507,20 +560,20 @@ static int drm_dp_i2c_xfer(struct i2c_adapter 
> > > *adapter, struct i2c_msg *msgs,
> > >           err = drm_dp_i2c_do_msg(aux, &msg);
> > >           if (err < 0)
> > >                   break;
> > > -         /*
> > > -          * Many hardware implementations support FIFOs larger than a
> > > -          * single byte, but it has been empirically determined that
> > > -          * transferring data in larger chunks can actually lead to
> > > -          * decreased performance. Therefore each message is simply
> > > -          * transferred byte-by-byte.
> > > +         /* We want each transaction to be as large as possible, but
> > > +          * we'll go to smaller sizes if the hardware gives us a
> > > +          * short reply.
> > >            */
> > > -         for (j = 0; j < msgs[i].len; j++) {
> > > +         transfer_size = dp_aux_i2c_transfer_size;
> > > +         for (j = 0; j < msgs[i].len; j += msg.size) {
> > >                   msg.buffer = msgs[i].buf + j;
> > > -                 msg.size = 1;
> > > +                 msg.size = min((unsigned)transfer_size, msgs[i].len - 
> > > j);
> > 
> > Could make transfer_size unsigned in the first place.
> > 
> > >  
> > > -                 err = drm_dp_i2c_do_msg(aux, &msg);
> > > -                 if (err < 0)
> > > +                 transfer_size = drm_dp_i2c_drain_msg(aux, &msg);
> > > +                 if (transfer_size < 0) {
> > > +                         err = transfer_size;
> > >                           break;
> > > +                 }
> > 
> > Maybe this is a bit more straight forward?
> > 
> > err = drm_dp_i2c_drain_msg(aux, &msg);
> > if (err < 0)
> >     break;
> > transfer_size = err;
> >
> That and making transfer_size unsigned seems like a good combination. Will
> adopt for v4.
> 
> -- 
> Simon Farnsworth
> Software Engineer
> ONELAN Ltd
> http://www.onelan.com



-- 
Ville Syrjälä
Intel OTC

Reply via email to