diff mbox

Add qword read/write support.

Message ID 1246912380-28786-2-git-send-email-turkal@google.com (mailing list archive)
State Not Applicable, archived
Headers show

Commit Message

Warren Turkal July 6, 2009, 8:33 p.m. UTC
Signed-off-by: Warren Turkal <turkal@google.com>
---
 lib/access.c |   15 +++++++++++++++
 lib/pci.h    |    2 ++
 lib/sysdep.h |   18 ++++++++++++++++++
 lib/types.h  |   35 +++++++++++++++++++++++------------
 setpci.c     |   18 +++++++++++++++---
 setpci.man   |    8 +++++---
 6 files changed, 78 insertions(+), 18 deletions(-)

Comments

Martin Mareš July 6, 2009, 9:42 p.m. UTC | #1
Hello!

>  #elif defined(PCI_HAVE_STDINT_H) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L)
> -#include <stdint.h>
> +#include <inttypes.h>

Even though it is probably OK to assume that <inttypes.h> exists whenever
<stdint.h> does, keeping using PCI_HAVE_STDINT_H for that is confusing :)

>  #else
> +#include <inttypes.h>
>  typedef u_int8_t u8;
>  typedef u_int16_t u16;
>  typedef u_int32_t u32;
> +typedef u_int64_t u64;

As I already said, we cannot rely on <inttypes.h> on non-C99 systems.

Also, we have to define the printf sequences ourselves.

>  #ifdef PCI_HAVE_64BIT_ADDRESS
>  #include <limits.h>
>  #if ULONG_MAX > 0xffffffff
> -typedef unsigned long u64;
> -#define PCI_U64_FMT "l"
> +typedef unsigned long pciaddr_t;
>  #else
> -typedef unsigned long long u64;
> -#define PCI_U64_FMT "ll"
> -#endif
> +typedef unsigned long long pciaddr_t;
>  #endif
>  
> -#endif	/* PCI_HAVE_Uxx_TYPES */
> +#define PCIADDR_T_FMT "%08" PRIu64 "x"
> +#define PCIADDR_PORT_FMT "%04" PRIu64 "x"

No, this is wrong. Since pciaddr_t is no longer u64, we must not use u64
printf sequences for it.

> -  const char * const formats[] = { NULL, " %02x", " %04x", NULL, " %08x" };
> -  const char * const mask_formats[] = { NULL, " %02x->(%02x:%02x)->%02x", " %04x->(%04x:%04x)->%04x", NULL, " %08x->(%08x:%08x)->%08x" };
> -  unsigned int i, x, y;
> +  const char * const formats[] = { NULL, " %02x", " %04x", NULL, " %08x", NULL, NULL, NULL, " %016" PRIu64 "x"};
> +  const char * const mask_formats[] = { NULL, " %02x->(%02x:%02x)->%02x", " %04x->(%04x:%04x)->%04x", NULL, " %08x->(%08x:%08x)->%08x", NULL, NULL, NULL, " %016" PRIu64 "x->(%016" PRIu64 "x:%016" PRIu64 "x)->%016" PRIu64 "x"};
> +  unsigned int i;
> +  u64 x, y;
>    int addr = 0;
>    int width = op->width;
>    char slot[16];
> @@ -120,6 +121,9 @@ exec_op(struct op *op, struct pci_dev *dev)
>  		case 2:
>  		  y = pci_read_word(dev, addr);
>  		  break;
> +		case 8:
> +		  y = pci_read_quad(dev, addr);
> +		  break;

This is also wrong. You have changed x,y to u64, but when you read a smaller
value to them, you try to print them with a non-u64 format string.

				Have a nice fortnight
Warren Turkal July 6, 2009, 10:36 p.m. UTC | #2
On Mon, Jul 6, 2009 at 14:42, Martin Mares<mj@ucw.cz> wrote:
>>  #elif defined(PCI_HAVE_STDINT_H) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L)
>> -#include <stdint.h>
>> +#include <inttypes.h>
>
> Even though it is probably OK to assume that <inttypes.h> exists whenever
> <stdint.h> does, keeping using PCI_HAVE_STDINT_H for that is confusing :)

Are you suggesting just removing the defined(PCI_HAVE_STDINT_H) part?

Also, I looked at the configure script and it looks like
PCI_HAVE_STDINT_H is only defined on sunos. That doesn't make much
since to me. There are only two references to that symbol in all the
source code. One in the lib/configure script and one in types.h. Shall
I just remove them both?

>>  #else
>> +#include <inttypes.h>
>>  typedef u_int8_t u8;
>>  typedef u_int16_t u16;
>>  typedef u_int32_t u32;
>> +typedef u_int64_t u64;
>
> As I already said, we cannot rely on <inttypes.h> on non-C99 systems.

For non-MS Windows systems, what systems do we support that don't have
those c99 headers?

I found that AIX 4.x, which has been end of lifed for a while, doesn't
have the headers, but AIX 5.3 does. Do we really need to maintain
support of systems as old as AIX 4.x? Could we take a more pragmatic
approach and introduce a change that may break the platform if I
promise to work with a user to get it building on AIX 4 if they really
need it. I doubt that and AIX 4 user would need new pciutils anyhow.

Also, do we have a list of supported platforms somewhere?

> Also, we have to define the printf sequences ourselves.

True. I hadn't solved that one yet. :(

>>  #ifdef PCI_HAVE_64BIT_ADDRESS
>>  #include <limits.h>
>>  #if ULONG_MAX > 0xffffffff
>> -typedef unsigned long u64;
>> -#define PCI_U64_FMT "l"
>> +typedef unsigned long pciaddr_t;
>>  #else
>> -typedef unsigned long long u64;
>> -#define PCI_U64_FMT "ll"
>> -#endif
>> +typedef unsigned long long pciaddr_t;
>>  #endif
>>
>> -#endif       /* PCI_HAVE_Uxx_TYPES */
>> +#define PCIADDR_T_FMT "%08" PRIu64 "x"
>> +#define PCIADDR_PORT_FMT "%04" PRIu64 "x"
>
> No, this is wrong. Since pciaddr_t is no longer u64, we must not use u64
> printf sequences for it.

This is reasonable.

>> -  const char * const formats[] = { NULL, " %02x", " %04x", NULL, " %08x" };
>> -  const char * const mask_formats[] = { NULL, " %02x->(%02x:%02x)->%02x", " %04x->(%04x:%04x)->%04x", NULL, " %08x->(%08x:%08x)->%08x" };
>> -  unsigned int i, x, y;
>> +  const char * const formats[] = { NULL, " %02x", " %04x", NULL, " %08x", NULL, NULL, NULL, " %016" PRIu64 "x"};
>> +  const char * const mask_formats[] = { NULL, " %02x->(%02x:%02x)->%02x", " %04x->(%04x:%04x)->%04x", NULL, " %08x->(%08x:%08x)->%08x", NULL, NULL, NULL, " %016" PRIu64 "x->(%016" PRIu64 "x:%016" PRIu64 "x)->%016" PRIu64 "x"};
>> +  unsigned int i;
>> +  u64 x, y;
>>    int addr = 0;
>>    int width = op->width;
>>    char slot[16];
>> @@ -120,6 +121,9 @@ exec_op(struct op *op, struct pci_dev *dev)
>>               case 2:
>>                 y = pci_read_word(dev, addr);
>>                 break;
>> +             case 8:
>> +               y = pci_read_quad(dev, addr);
>> +               break;
>
> This is also wrong. You have changed x,y to u64, but when you read a smaller
> value to them, you try to print them with a non-u64 format string.

I don't understand this comment fully. Previously, these values were
u32. Didn't they get printed the same way, that is using the non-u32
format strings?

Thanks,
wt
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Martin Mareš July 7, 2009, 11:02 a.m. UTC | #3
Hello!

> > Even though it is probably OK to assume that <inttypes.h> exists whenever
> > <stdint.h> does, keeping using PCI_HAVE_STDINT_H for that is confusing :)
> 
> Are you suggesting just removing the defined(PCI_HAVE_STDINT_H) part?

I suggest either removing the test altogether (if we come to a conclusion
that we can rely on C99 includes everywhere), or renaming the symbol
to PCI_HAVE_INTTYPES_H.

> Also, I looked at the configure script and it looks like
> PCI_HAVE_STDINT_H is only defined on sunos. That doesn't make much
> since to me.

AFAIK old versions of Solaris had some C99 includes, but not a C99 compiler,
so the STDC_VERSION test did not trigger.

> For non-MS Windows systems, what systems do we support that don't have
> those c99 headers?

As I have already said, I am not sure about that as I have never seen
some of the supported systems myself. Current versions of all systems
except Windows should be safe, but older versions frequently lack them.

> I found that AIX 4.x, which has been end of lifed for a while, doesn't
> have the headers, but AIX 5.3 does. Do we really need to maintain
> support of systems as old as AIX 4.x?

Judging from the occasional bug reports I receive, people still use such
systems, so as long as supporting the old systems is a matter of a couple of
lines, I would really like not to break them.

I would therefore keep the fallback to u_intXX_t types, but instead of u_int64_t
(for which we do not know the format sequence) use either long or long long,
depending on ULONG_MAX.

> Also, do we have a list of supported platforms somewhere?

They are listed in README, but only names of the platforms, not specific versions.

> I don't understand this comment fully. Previously, these values were
> u32. Didn't they get printed the same way, that is using the non-u32
> format strings?

In exec_op(), we call:

	printf(formats[width]+1, x);

(and similarly trace()). I.e., we are printing the same variable `x', but
with different format strings. As long as the format strings were "%<something>x",
it worked, because all those required `unsigned int' as an argument. If you
change `x' to u64, you have to change _all_ format strings used on it.

				Have a nice fortnight
Rolf Eike Beer July 7, 2009, 11:06 a.m. UTC | #4
Warren Turkal wrote:

> diff --git a/lib/types.h b/lib/types.h
> index 3e0e5c3..9bdd82a 100644
> --- a/lib/types.h
> +++ b/lib/types.h
> @@ -15,39 +15,50 @@
>  typedef BYTE u8;
>  typedef WORD u16;
>  typedef DWORD u32;
> +typedef I64u u64;
> +
> +#ifdef PCI_HAVE_64BIT_ADDRESS
> +#include <limits.h>
> +#if ULONG_MAX > 0xffffffff
> +#define PRIu64 "l"
> +#else
> +#define PRIu64 "ll"
> +#endif
> +#endif
> +

Ehm, no, the other way round:

typedef unsigned __int64 u64;
#define PRIu64 I64u

Greetings,

Eike
Warren Turkal July 8, 2009, 1:24 a.m. UTC | #5
On Tue, Jul 7, 2009 at 04:02, Martin Mares<mj@ucw.cz> wrote:
>> > Even though it is probably OK to assume that <inttypes.h> exists whenever
>> > <stdint.h> does, keeping using PCI_HAVE_STDINT_H for that is confusing :)
>>
>> Are you suggesting just removing the defined(PCI_HAVE_STDINT_H) part?
>
> I suggest either removing the test altogether (if we come to a conclusion
> that we can rely on C99 includes everywhere), or renaming the symbol
> to PCI_HAVE_INTTYPES_H.

From my research, inttypes.h and stdint.h has existed since the listed
timeframe for each of the following systems. I looked for evidence in
public source repositories and on mailing lists of folks trying to
compile software.

Linux
GNU/kFreeBSD
GNU Hurd
-These 3 are dependant on GNU libc which has had the files since the late '90s.

FreeBSD - since at least 5.x, which is long out of even legacy
releases and was released in 2003.
NetBSD - since 2001 or so at least
OpenBSD - since at least 2003
AIX - at least since version 5.1, which was released in 2001 and end
of lifed in 2006. 4.3.3, which didn't have the files, was EOLed in
2003.

Windows - The best one can do is a third party implementation.

CYGWIN - don't know

Solaris (since Solaris 10 at least)
- There is evidence that Solaris 8 or 9 may not have had inttypes.h,
but I think it's just that you had to get the sun's compilers (or
another c library) to get the header file.

Having said all this, I suggest we remove the test altogether, which
is what I have sitting in my take 7 version of the patch. I also
suggest that we drop the non-C99 types (like u_int32_t), as I suspect
that the uint32_t style types will probably work pretty far back
considering the above observations. I think that we can safely say
that we are covering at least 6 years worth of development
environments (back to 2003), if not more.

Here is my proposal, for that section in the types.h, I don't think
that we need the test for C99. It should probably look more like the
following:

#ifndef PCI_HAVE_Uxx_TYPES

#ifdef PCI_OS_WINDOWS
#include <windef.h>
typedef BYTE u8;
typedef WORD u16;
typedef DWORD u32;
typedef unsigned __int64 u64;

//define printf format strings for pci address for windows here

#else
#include <inttypes.h>
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;

//define printf format strings for everyone else here

#endif

#endif>·/* PCI_HAVE_Uxx_TYPES */

I think this will work on all the above systems since 2003 at least.

>> Also, I looked at the configure script and it looks like
>> PCI_HAVE_STDINT_H is only defined on sunos. That doesn't make much
>> since to me.
>
> AFAIK old versions of Solaris had some C99 includes, but not a C99 compiler,
> so the STDC_VERSION test did not trigger.

What would you think about trying to remove that STDC_VERSION test
given the above info?

>> For non-MS Windows systems, what systems do we support that don't have
>> those c99 headers?
>
> As I have already said, I am not sure about that as I have never seen
> some of the supported systems myself. Current versions of all systems
> except Windows should be safe, but older versions frequently lack them.

Windows can be worked around. As for the other system, does the info
above make you feel any more confident?

>> I found that AIX 4.x, which has been end of lifed for a while, doesn't
>> have the headers, but AIX 5.3 does. Do we really need to maintain
>> support of systems as old as AIX 4.x?
>
> Judging from the occasional bug reports I receive, people still use such
> systems, so as long as supporting the old systems is a matter of a couple of
> lines, I would really like not to break them.
>
> I would therefore keep the fallback to u_intXX_t types, but instead of u_int64_t
> (for which we do not know the format sequence) use either long or long long,
> depending on ULONG_MAX.

Would you be willing to remove the u_intXX_t style types given the above info?

>> Also, do we have a list of supported platforms somewhere?
>
> They are listed in README, but only names of the platforms, not specific versions.

Thanks for the pointer. That's where I got the list of platforms to
check for which versions had inttypes.h and stdint.h.

>> I don't understand this comment fully. Previously, these values were
>> u32. Didn't they get printed the same way, that is using the non-u32
>> format strings?
>
> In exec_op(), we call:
>
>        printf(formats[width]+1, x);
>
> (and similarly trace()). I.e., we are printing the same variable `x', but
> with different format strings. As long as the format strings were "%<something>x",
> it worked, because all those required `unsigned int' as an argument. If you
> change `x' to u64, you have to change _all_ format strings used on it.

I see. Thanks for the explanation.

wt
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Warren Turkal July 8, 2009, 1:27 a.m. UTC | #6
Thanks,

Updated for take 7.

wt

On Tue, Jul 7, 2009 at 04:06, Rolf Eike Beer<eike-kernel@sf-tec.de> wrote:
> Warren Turkal wrote:
>
>> diff --git a/lib/types.h b/lib/types.h
>> index 3e0e5c3..9bdd82a 100644
>> --- a/lib/types.h
>> +++ b/lib/types.h
>> @@ -15,39 +15,50 @@
>>  typedef BYTE u8;
>>  typedef WORD u16;
>>  typedef DWORD u32;
>> +typedef I64u u64;
>> +
>> +#ifdef PCI_HAVE_64BIT_ADDRESS
>> +#include <limits.h>
>> +#if ULONG_MAX > 0xffffffff
>> +#define PRIu64 "l"
>> +#else
>> +#define PRIu64 "ll"
>> +#endif
>> +#endif
>> +
>
> Ehm, no, the other way round:
>
> typedef unsigned __int64 u64;
> #define PRIu64 I64u
>
> Greetings,
>
> Eike
>
--
To unsubscribe from this list: send the line "unsubscribe linux-pci" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/lib/access.c b/lib/access.c
index 691df39..b8addea 100644
--- a/lib/access.c
+++ b/lib/access.c
@@ -98,6 +98,14 @@  pci_read_long(struct pci_dev *d, int pos)
   return le32_to_cpu(buf);
 }
 
+u64
+pci_read_quad(struct pci_dev *d, int pos)
+{
+  u64 buf;
+  pci_read_data(d, &buf, pos, 8);
+  return le64_to_cpu(buf);
+}
+
 int
 pci_read_block(struct pci_dev *d, int pos, byte *buf, int len)
 {
@@ -141,6 +149,13 @@  pci_write_long(struct pci_dev *d, int pos, u32 data)
 }
 
 int
+pci_write_quad(struct pci_dev *d, int pos, u64 data)
+{
+  u64 buf = cpu_to_le64(data);
+  return pci_write_data(d, &buf, pos, 8);
+}
+
+int
 pci_write_block(struct pci_dev *d, int pos, byte *buf, int len)
 {
   if (pos < d->cache_len)
diff --git a/lib/pci.h b/lib/pci.h
index 7a5a6b8..71d673f 100644
--- a/lib/pci.h
+++ b/lib/pci.h
@@ -147,11 +147,13 @@  struct pci_dev {
 u8 pci_read_byte(struct pci_dev *, int pos) PCI_ABI; /* Access to configuration space */
 u16 pci_read_word(struct pci_dev *, int pos) PCI_ABI;
 u32 pci_read_long(struct pci_dev *, int pos) PCI_ABI;
+u64 pci_read_quad(struct pci_dev *, int pos) PCI_ABI;
 int pci_read_block(struct pci_dev *, int pos, u8 *buf, int len) PCI_ABI;
 int pci_read_vpd(struct pci_dev *d, int pos, u8 *buf, int len) PCI_ABI;
 int pci_write_byte(struct pci_dev *, int pos, u8 data) PCI_ABI;
 int pci_write_word(struct pci_dev *, int pos, u16 data) PCI_ABI;
 int pci_write_long(struct pci_dev *, int pos, u32 data) PCI_ABI;
+int pci_write_quad(struct pci_dev *, int pos, u64 data) PCI_ABI;
 int pci_write_block(struct pci_dev *, int pos, u8 *buf, int len) PCI_ABI;
 
 int pci_fill_info(struct pci_dev *, int flags) PCI_ABI; /* Fill in device information */
diff --git a/lib/sysdep.h b/lib/sysdep.h
index 2a25c93..4701884 100644
--- a/lib/sysdep.h
+++ b/lib/sysdep.h
@@ -27,8 +27,10 @@  typedef u16 word;
 #include <asm/byteorder.h>
 #define cpu_to_le16 __cpu_to_le16
 #define cpu_to_le32 __cpu_to_le32
+#define cpu_to_le64 __cpu_to_le64
 #define le16_to_cpu __le16_to_cpu
 #define le32_to_cpu __le32_to_cpu
+#define le64_to_cpu __le64_to_cpu
 
 #else
 
@@ -63,8 +65,10 @@  typedef u16 word;
 #if BYTE_ORDER == BIG_ENDIAN
 #define cpu_to_le16 swab16
 #define cpu_to_le32 swab32
+#define cpu_to_le64 swab64
 #define le16_to_cpu swab16
 #define le32_to_cpu swab32
+#define le64_to_cpu swab64
 
 static inline word swab16(word w)
 {
@@ -78,11 +82,25 @@  static inline u32 swab32(u32 w)
          ((w & 0x0000ff00) << 8)  |
          ((w & 0x000000ff) << 24);
 }
+
+static inline u64 swab64(u64 w)
+{
+  return ((w & 0xff00000000000000) >> 56) |
+         ((w & 0x00ff000000000000) >> 40) |
+         ((w & 0x0000ff0000000000) >> 24) |
+         ((w & 0x000000ff00000000) >> 8) |
+         ((w & 0x00000000ff000000) << 8) |
+         ((w & 0x0000000000ff0000) << 24) |
+         ((w & 0x000000000000ff00) << 40) |
+         ((w & 0x00000000000000ff) << 56);
+}
 #else
 #define cpu_to_le16(x) (x)
 #define cpu_to_le32(x) (x)
+#define cpu_to_le64(x) (x)
 #define le16_to_cpu(x) (x)
 #define le32_to_cpu(x) (x)
+#define le64_to_cpu(x) (x)
 #endif
 
 #endif
diff --git a/lib/types.h b/lib/types.h
index 3e0e5c3..9bdd82a 100644
--- a/lib/types.h
+++ b/lib/types.h
@@ -15,39 +15,50 @@ 
 typedef BYTE u8;
 typedef WORD u16;
 typedef DWORD u32;
+typedef I64u u64;
+
+#ifdef PCI_HAVE_64BIT_ADDRESS
+#include <limits.h>
+#if ULONG_MAX > 0xffffffff
+#define PRIu64 "l"
+#else
+#define PRIu64 "ll"
+#endif
+#endif
+
 #elif defined(PCI_HAVE_STDINT_H) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L)
-#include <stdint.h>
+#include <inttypes.h>
 typedef uint8_t u8;
 typedef uint16_t u16;
 typedef uint32_t u32;
+typedef uint64_t u64;
 #else
+#include <inttypes.h>
 typedef u_int8_t u8;
 typedef u_int16_t u16;
 typedef u_int32_t u32;
+typedef u_int64_t u64;
 #endif
 
+#endif	/* PCI_HAVE_Uxx_TYPES */
+
 #ifdef PCI_HAVE_64BIT_ADDRESS
 #include <limits.h>
 #if ULONG_MAX > 0xffffffff
-typedef unsigned long u64;
-#define PCI_U64_FMT "l"
+typedef unsigned long pciaddr_t;
 #else
-typedef unsigned long long u64;
-#define PCI_U64_FMT "ll"
-#endif
+typedef unsigned long long pciaddr_t;
 #endif
 
-#endif	/* PCI_HAVE_Uxx_TYPES */
+#define PCIADDR_T_FMT "%08" PRIu64 "x"
+#define PCIADDR_PORT_FMT "%04" PRIu64 "x"
 
-#ifdef PCI_HAVE_64BIT_ADDRESS
-typedef u64 pciaddr_t;
-#define PCIADDR_T_FMT "%08" PCI_U64_FMT "x"
-#define PCIADDR_PORT_FMT "%04" PCI_U64_FMT "x"
 #else
 typedef u32 pciaddr_t;
 #define PCIADDR_T_FMT "%08x"
 #define PCIADDR_PORT_FMT "%04x"
-#endif
+
+#endif /* PCI_HAVE_64BIT_ADDRESS */
 
 #ifdef PCI_ARCH_SPARC64
 /* On sparc64 Linux the kernel reports remapped port addresses and IRQ numbers */
diff --git a/setpci.c b/setpci.c
index 97740e8..ebc89bc 100644
--- a/setpci.c
+++ b/setpci.c
@@ -73,9 +73,10 @@  trace(const char *fmt, ...)
 static void
 exec_op(struct op *op, struct pci_dev *dev)
 {
-  const char * const formats[] = { NULL, " %02x", " %04x", NULL, " %08x" };
-  const char * const mask_formats[] = { NULL, " %02x->(%02x:%02x)->%02x", " %04x->(%04x:%04x)->%04x", NULL, " %08x->(%08x:%08x)->%08x" };
-  unsigned int i, x, y;
+  const char * const formats[] = { NULL, " %02x", " %04x", NULL, " %08x", NULL, NULL, NULL, " %016" PRIu64 "x"};
+  const char * const mask_formats[] = { NULL, " %02x->(%02x:%02x)->%02x", " %04x->(%04x:%04x)->%04x", NULL, " %08x->(%08x:%08x)->%08x", NULL, NULL, NULL, " %016" PRIu64 "x->(%016" PRIu64 "x:%016" PRIu64 "x)->%016" PRIu64 "x"};
+  unsigned int i;
+  u64 x, y;
   int addr = 0;
   int width = op->width;
   char slot[16];
@@ -120,6 +121,9 @@  exec_op(struct op *op, struct pci_dev *dev)
 		case 2:
 		  y = pci_read_word(dev, addr);
 		  break;
+		case 8:
+		  y = pci_read_quad(dev, addr);
+		  break;
 		default:
 		  y = pci_read_long(dev, addr);
 		  break;
@@ -137,6 +141,9 @@  exec_op(struct op *op, struct pci_dev *dev)
 		case 2:
 		  pci_write_word(dev, addr, x);
 		  break;
+		case 8:
+		  pci_write_quad(dev, addr, x);
+		  break;
 		default:
 		  pci_write_long(dev, addr, x);
 		  break;
@@ -157,6 +164,9 @@  exec_op(struct op *op, struct pci_dev *dev)
 	case 2:
 	  x = pci_read_word(dev, addr);
 	  break;
+	case 8:
+	  x = pci_read_quad(dev, addr);
+	  break;
 	default:
 	  x = pci_read_long(dev, addr);
 	  break;
@@ -600,6 +610,8 @@  static void parse_op(char *c, struct pci_dev **selected_devices)
 	  op->width = 2; break;
 	case 'L':
 	  op->width = 4; break;
+	case 'Q':
+	  op->width = 8; break;
 	default:
 	  parse_err("Invalid width \"%c\"", *width);
 	}
diff --git a/setpci.man b/setpci.man
index 6c1ad8c..f4ba7d4 100644
--- a/setpci.man
+++ b/setpci.man
@@ -149,9 +149,11 @@  Each of the previous formats can be followed by \fB+offset\fP to add an offset
 (a hex number) to the address. This feature can be useful for addressing of registers
 living within a capability, or to modify parts of standard registers.
 \IP \(bu
-Finally, you should append a width specifier \fB.B\fP, \fB.W\fP, or \fB.L\fP to choose
-how many bytes (1, 2, or 4) should be transferred. The width can be omitted if you are
-accessing a named register whose width is well known.
+Finally, you should append a width specifier \fB.B\fP, \fB.W\fP, \fB.L\fP, or \fB.Q\fP
+to choose how many bytes (1, 2, 4, or 8) should be transferred. At the time of writing,
+8 byte wide read/writes are not atomic, and are executed as two 4 byte reads/writes.
+The width can be omitted if you are accessing a named register whose width is well
+known.
 
 .PP
 All names of registers and width specifiers are case-insensitive.