Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
L
linux
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
linux
Commits
e19f970b
Commit
e19f970b
authored
Apr 05, 2002
by
Bjorn Helgaas
Committed by
David Mosberger
Apr 05, 2002
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[PATCH] Add support for HP zx1 systems.
parent
5dbc562f
Changes
26
Hide whitespace changes
Inline
Side-by-side
Showing
26 changed files
with
2489 additions
and
15 deletions
+2489
-15
arch/ia64/Makefile
arch/ia64/Makefile
+12
-8
arch/ia64/config.in
arch/ia64/config.in
+2
-1
arch/ia64/dig/setup.c
arch/ia64/dig/setup.c
+0
-2
arch/ia64/hp/common/Makefile
arch/ia64/hp/common/Makefile
+14
-0
arch/ia64/hp/common/sba_iommu.c
arch/ia64/hp/common/sba_iommu.c
+1850
-0
arch/ia64/hp/sim/Makefile
arch/ia64/hp/sim/Makefile
+13
-0
arch/ia64/hp/sim/hpsim_console.c
arch/ia64/hp/sim/hpsim_console.c
+0
-0
arch/ia64/hp/sim/hpsim_irq.c
arch/ia64/hp/sim/hpsim_irq.c
+0
-0
arch/ia64/hp/sim/hpsim_machvec.c
arch/ia64/hp/sim/hpsim_machvec.c
+0
-0
arch/ia64/hp/sim/hpsim_setup.c
arch/ia64/hp/sim/hpsim_setup.c
+0
-0
arch/ia64/hp/sim/hpsim_ssc.h
arch/ia64/hp/sim/hpsim_ssc.h
+0
-0
arch/ia64/hp/zx1/Makefile
arch/ia64/hp/zx1/Makefile
+13
-0
arch/ia64/hp/zx1/hpzx1_machvec.c
arch/ia64/hp/zx1/hpzx1_machvec.c
+2
-0
arch/ia64/hp/zx1/hpzx1_misc.c
arch/ia64/hp/zx1/hpzx1_misc.c
+400
-0
arch/ia64/kernel/Makefile
arch/ia64/kernel/Makefile
+1
-0
arch/ia64/kernel/acpi.c
arch/ia64/kernel/acpi.c
+101
-3
arch/ia64/kernel/ia64_ksyms.c
arch/ia64/kernel/ia64_ksyms.c
+7
-0
arch/ia64/kernel/irq.c
arch/ia64/kernel/irq.c
+17
-0
arch/ia64/kernel/setup.c
arch/ia64/kernel/setup.c
+2
-0
arch/ia64/mm/tlb.c
arch/ia64/mm/tlb.c
+1
-1
drivers/pci/pci.ids
drivers/pci/pci.ids
+3
-0
include/asm-ia64/hw_irq.h
include/asm-ia64/hw_irq.h
+2
-0
include/asm-ia64/machvec.h
include/asm-ia64/machvec.h
+4
-0
include/asm-ia64/machvec_hpzx1.h
include/asm-ia64/machvec_hpzx1.h
+37
-0
include/asm-ia64/machvec_init.h
include/asm-ia64/machvec_init.h
+5
-0
include/linux/pci_ids.h
include/linux/pci_ids.h
+3
-0
No files found.
arch/ia64/Makefile
View file @
e19f970b
...
@@ -33,16 +33,11 @@ ifeq ($(CONFIG_ITANIUM_BSTEP_SPECIFIC),y)
...
@@ -33,16 +33,11 @@ ifeq ($(CONFIG_ITANIUM_BSTEP_SPECIFIC),y)
endif
endif
ifdef
CONFIG_IA64_GENERIC
ifdef
CONFIG_IA64_GENERIC
CORE_FILES
:=
arch
/
$(ARCH)
/hp/hp.a
\
CORE_FILES
:=
arch
/
$(ARCH)
/hp/hp.o
\
arch
/
$(ARCH)
/sn/sn.o
\
arch
/
$(ARCH)
/dig/dig.a
\
arch
/
$(ARCH)
/dig/dig.a
\
arch
/
$(ARCH)
/sn/io/sgiio.o
\
$(CORE_FILES)
$(CORE_FILES)
SUBDIRS
:=
arch
/
$(ARCH)
/hp
\
SUBDIRS
:=
arch
/
$(ARCH)
/hp
\
arch
/
$(ARCH)
/sn/sn1
\
arch
/
$(ARCH)
/sn
\
arch
/
$(ARCH)
/dig
\
arch
/
$(ARCH)
/dig
\
arch
/
$(ARCH)
/sn/io
\
$(SUBDIRS)
$(SUBDIRS)
else
# !GENERIC
else
# !GENERIC
...
@@ -50,7 +45,16 @@ else # !GENERIC
...
@@ -50,7 +45,16 @@ else # !GENERIC
ifdef
CONFIG_IA64_HP_SIM
ifdef
CONFIG_IA64_HP_SIM
SUBDIRS
:=
arch
/
$(ARCH)
/hp
\
SUBDIRS
:=
arch
/
$(ARCH)
/hp
\
$(SUBDIRS)
$(SUBDIRS)
CORE_FILES
:=
arch
/
$(ARCH)
/hp/hp.a
\
CORE_FILES
:=
arch
/
$(ARCH)
/hp/hp.o
\
$(CORE_FILES)
endif
ifdef
CONFIG_IA64_HP_ZX1
SUBDIRS
:=
arch
/
$(ARCH)
/hp
\
arch
/
$(ARCH)
/dig
\
$(SUBDIRS)
CORE_FILES
:=
arch
/
$(ARCH)
/hp/hp.o
\
arch
/
$(ARCH)
/dig/dig.a
\
$(CORE_FILES)
$(CORE_FILES)
endif
endif
...
...
arch/ia64/config.in
View file @
e19f970b
...
@@ -22,6 +22,7 @@ choice 'IA-64 system type' \
...
@@ -22,6 +22,7 @@ choice 'IA-64 system type' \
"generic CONFIG_IA64_GENERIC \
"generic CONFIG_IA64_GENERIC \
DIG-compliant CONFIG_IA64_DIG \
DIG-compliant CONFIG_IA64_DIG \
HP-simulator CONFIG_IA64_HP_SIM \
HP-simulator CONFIG_IA64_HP_SIM \
HP-zx1 CONFIG_IA64_HP_ZX1 \
SGI-SN1 CONFIG_IA64_SGI_SN1 \
SGI-SN1 CONFIG_IA64_SGI_SN1 \
SGI-SN2 CONFIG_IA64_SGI_SN2" generic
SGI-SN2 CONFIG_IA64_SGI_SN2" generic
...
@@ -56,7 +57,7 @@ if [ "$CONFIG_MCKINLEY" = "y" ]; then
...
@@ -56,7 +57,7 @@ if [ "$CONFIG_MCKINLEY" = "y" ]; then
fi
fi
fi
fi
if [ "$CONFIG_IA64_
DIG
" = "y" ]; then
if [ "$CONFIG_IA64_
GENERIC" = "y" ] || [ "$CONFIG_IA64_DIG" = "y" ] || [ "$CONFIG_IA64_HP_ZX1
" = "y" ]; then
bool ' Enable IA-64 Machine Check Abort' CONFIG_IA64_MCA
bool ' Enable IA-64 Machine Check Abort' CONFIG_IA64_MCA
define_bool CONFIG_PM y
define_bool CONFIG_PM y
fi
fi
...
...
arch/ia64/dig/setup.c
View file @
e19f970b
...
@@ -35,8 +35,6 @@
...
@@ -35,8 +35,6 @@
char
drive_info
[
4
*
16
];
char
drive_info
[
4
*
16
];
extern
int
pcat_compat
;
extern
int
pcat_compat
;
unsigned
char
aux_device_present
=
0xaa
;
/* XXX remove this when legacy I/O is gone */
void
__init
void
__init
dig_setup
(
char
**
cmdline_p
)
dig_setup
(
char
**
cmdline_p
)
{
{
...
...
arch/ia64/hp/common/Makefile
View file @
e19f970b
...
@@ -12,3 +12,17 @@ export-objs := sba_iommu.o
...
@@ -12,3 +12,17 @@ export-objs := sba_iommu.o
obj-y
:=
sba_iommu.o
obj-y
:=
sba_iommu.o
include
$(TOPDIR)/Rules.make
include
$(TOPDIR)/Rules.make
#
# ia64/platform/hp/common/Makefile
#
# Copyright (C) 2002 Hewlett Packard
# Copyright (C) Alex Williamson (alex_williamson@hp.com)
#
O_TARGET
:=
common.o
export-objs
:=
sba_iommu.o
obj-y
:=
sba_iommu.o
include
$(TOPDIR)/Rules.make
arch/ia64/hp/common/sba_iommu.c
View file @
e19f970b
...
@@ -1848,3 +1848,1853 @@ EXPORT_SYMBOL(sba_unmap_sg);
...
@@ -1848,3 +1848,1853 @@ EXPORT_SYMBOL(sba_unmap_sg);
EXPORT_SYMBOL
(
sba_dma_address
);
EXPORT_SYMBOL
(
sba_dma_address
);
EXPORT_SYMBOL
(
sba_alloc_consistent
);
EXPORT_SYMBOL
(
sba_alloc_consistent
);
EXPORT_SYMBOL
(
sba_free_consistent
);
EXPORT_SYMBOL
(
sba_free_consistent
);
/*
** IA64 System Bus Adapter (SBA) I/O MMU manager
**
** (c) Copyright 2002 Alex Williamson
** (c) Copyright 2002 Hewlett-Packard Company
**
** Portions (c) 2000 Grant Grundler (from parisc I/O MMU code)
** Portions (c) 1999 Dave S. Miller (from sparc64 I/O MMU code)
**
** 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 module initializes the IOC (I/O Controller) found on HP
** McKinley machines and their successors.
**
*/
#include <linux/config.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/string.h>
#include <linux/pci.h>
#include <linux/proc_fs.h>
#include <asm/delay.h>
/* ia64_get_itc() */
#include <asm/io.h>
#include <asm/page.h>
/* PAGE_OFFSET */
#include <asm/efi.h>
#define DRIVER_NAME "SBA"
#ifndef CONFIG_IA64_HP_PROTO
#define ALLOW_IOV_BYPASS
#endif
#define ENABLE_MARK_CLEAN
/*
** The number of debug flags is a clue - this code is fragile.
*/
#undef DEBUG_SBA_INIT
#undef DEBUG_SBA_RUN
#undef DEBUG_SBA_RUN_SG
#undef DEBUG_SBA_RESOURCE
#undef ASSERT_PDIR_SANITY
#undef DEBUG_LARGE_SG_ENTRIES
#undef DEBUG_BYPASS
#define SBA_INLINE __inline__
/* #define SBA_INLINE */
#ifdef DEBUG_SBA_INIT
#define DBG_INIT(x...) printk(x)
#else
#define DBG_INIT(x...)
#endif
#ifdef DEBUG_SBA_RUN
#define DBG_RUN(x...) printk(x)
#else
#define DBG_RUN(x...)
#endif
#ifdef DEBUG_SBA_RUN_SG
#define DBG_RUN_SG(x...) printk(x)
#else
#define DBG_RUN_SG(x...)
#endif
#ifdef DEBUG_SBA_RESOURCE
#define DBG_RES(x...) printk(x)
#else
#define DBG_RES(x...)
#endif
#ifdef DEBUG_BYPASS
#define DBG_BYPASS(x...) printk(x)
#else
#define DBG_BYPASS(x...)
#endif
#ifdef ASSERT_PDIR_SANITY
#define ASSERT(expr) \
if(!(expr)) { \
printk( "\n" __FILE__ ":%d: Assertion " #expr " failed!\n",__LINE__); \
panic(#expr); \
}
#else
#define ASSERT(expr)
#endif
#define KB(x) ((x) * 1024)
#define MB(x) (KB (KB (x)))
#define GB(x) (MB (KB (x)))
/*
** The number of pdir entries to "free" before issueing
** a read to PCOM register to flush out PCOM writes.
** Interacts with allocation granularity (ie 4 or 8 entries
** allocated and free'd/purged at a time might make this
** less interesting).
*/
#define DELAYED_RESOURCE_CNT 16
#define DEFAULT_DMA_HINT_REG 0
#define ZX1_FUNC_ID_VALUE ((PCI_DEVICE_ID_HP_ZX1_SBA << 16) | PCI_VENDOR_ID_HP)
#define ZX1_MC_ID ((PCI_DEVICE_ID_HP_ZX1_MC << 16) | PCI_VENDOR_ID_HP)
#define SBA_FUNC_ID 0x0000
/* function id */
#define SBA_FCLASS 0x0008
/* function class, bist, header, rev... */
#define SBA_FUNC_SIZE 0x10000
/* SBA configuration function reg set */
unsigned
int
__initdata
zx1_func_offsets
[]
=
{
0x1000
,
0x4000
,
0x8000
,
0x9000
,
0xa000
,
-
1
};
#define SBA_IOC_OFFSET 0x1000
#define MAX_IOC 1
/* we only have 1 for now*/
#define IOC_IBASE 0x300
/* IO TLB */
#define IOC_IMASK 0x308
#define IOC_PCOM 0x310
#define IOC_TCNFG 0x318
#define IOC_PDIR_BASE 0x320
#define IOC_IOVA_SPACE_BASE 0x40000000
/* IOVA ranges start at 1GB */
/*
** IOC supports 4/8/16/64KB page sizes (see TCNFG register)
** It's safer (avoid memory corruption) to keep DMA page mappings
** equivalently sized to VM PAGE_SIZE.
**
** We really can't avoid generating a new mapping for each
** page since the Virtual Coherence Index has to be generated
** and updated for each page.
**
** IOVP_SIZE could only be greater than PAGE_SIZE if we are
** confident the drivers really only touch the next physical
** page iff that driver instance owns it.
*/
#define IOVP_SIZE PAGE_SIZE
#define IOVP_SHIFT PAGE_SHIFT
#define IOVP_MASK PAGE_MASK
struct
ioc
{
unsigned
long
ioc_hpa
;
/* I/O MMU base address */
char
*
res_map
;
/* resource map, bit == pdir entry */
u64
*
pdir_base
;
/* physical base address */
unsigned
long
ibase
;
/* pdir IOV Space base */
unsigned
long
imask
;
/* pdir IOV Space mask */
unsigned
long
*
res_hint
;
/* next avail IOVP - circular search */
spinlock_t
res_lock
;
unsigned
long
hint_mask_pdir
;
/* bits used for DMA hints */
unsigned
int
res_bitshift
;
/* from the RIGHT! */
unsigned
int
res_size
;
/* size of resource map in bytes */
unsigned
int
hint_shift_pdir
;
unsigned
long
dma_mask
;
#if DELAYED_RESOURCE_CNT > 0
int
saved_cnt
;
struct
sba_dma_pair
{
dma_addr_t
iova
;
size_t
size
;
}
saved
[
DELAYED_RESOURCE_CNT
];
#endif
#ifdef CONFIG_PROC_FS
#define SBA_SEARCH_SAMPLE 0x100
unsigned
long
avg_search
[
SBA_SEARCH_SAMPLE
];
unsigned
long
avg_idx
;
/* current index into avg_search */
unsigned
long
used_pages
;
unsigned
long
msingle_calls
;
unsigned
long
msingle_pages
;
unsigned
long
msg_calls
;
unsigned
long
msg_pages
;
unsigned
long
usingle_calls
;
unsigned
long
usingle_pages
;
unsigned
long
usg_calls
;
unsigned
long
usg_pages
;
#ifdef ALLOW_IOV_BYPASS
unsigned
long
msingle_bypass
;
unsigned
long
usingle_bypass
;
unsigned
long
msg_bypass
;
#endif
#endif
/* STUFF We don't need in performance path */
unsigned
int
pdir_size
;
/* in bytes, determined by IOV Space size */
};
struct
sba_device
{
struct
sba_device
*
next
;
/* list of SBA's in system */
const
char
*
name
;
unsigned
long
sba_hpa
;
/* base address */
spinlock_t
sba_lock
;
unsigned
int
flags
;
/* state/functionality enabled */
unsigned
int
hw_rev
;
/* HW revision of chip */
unsigned
int
num_ioc
;
/* number of on-board IOC's */
struct
ioc
ioc
[
MAX_IOC
];
};
static
struct
sba_device
*
sba_list
;
static
int
sba_count
;
static
int
reserve_sba_gart
=
1
;
#define sba_sg_iova(sg) (sg->address)
#define sba_sg_len(sg) (sg->length)
#define sba_sg_buffer(sg) (sg->orig_address)
/* REVISIT - fix me for multiple SBAs/IOCs */
#define GET_IOC(dev) (sba_list->ioc)
#define SBA_SET_AGP(sba_dev) (sba_dev->flags |= 0x1)
#define SBA_GET_AGP(sba_dev) (sba_dev->flags & 0x1)
/*
** DMA_CHUNK_SIZE is used by the SCSI mid-layer to break up
** (or rather not merge) DMA's into managable chunks.
** On parisc, this is more of the software/tuning constraint
** rather than the HW. I/O MMU allocation alogorithms can be
** faster with smaller size is (to some degree).
*/
#define DMA_CHUNK_SIZE (BITS_PER_LONG*PAGE_SIZE)
/* Looks nice and keeps the compiler happy */
#define SBA_DEV(d) ((struct sba_device *) (d))
#define ROUNDUP(x,y) ((x + ((y)-1)) & ~((y)-1))
/************************************
** SBA register read and write support
**
** BE WARNED: register writes are posted.
** (ie follow writes which must reach HW with a read)
**
*/
#define READ_REG(addr) __raw_readq(addr)
#define WRITE_REG(val, addr) __raw_writeq(val, addr)
#ifdef DEBUG_SBA_INIT
/**
* sba_dump_tlb - debugging only - print IOMMU operating parameters
* @hpa: base address of the IOMMU
*
* Print the size/location of the IO MMU PDIR.
*/
static
void
sba_dump_tlb
(
char
*
hpa
)
{
DBG_INIT
(
"IO TLB at 0x%p
\n
"
,
(
void
*
)
hpa
);
DBG_INIT
(
"IOC_IBASE : %016lx
\n
"
,
READ_REG
(
hpa
+
IOC_IBASE
));
DBG_INIT
(
"IOC_IMASK : %016lx
\n
"
,
READ_REG
(
hpa
+
IOC_IMASK
));
DBG_INIT
(
"IOC_TCNFG : %016lx
\n
"
,
READ_REG
(
hpa
+
IOC_TCNFG
));
DBG_INIT
(
"IOC_PDIR_BASE: %016lx
\n
"
,
READ_REG
(
hpa
+
IOC_PDIR_BASE
));
DBG_INIT
(
"
\n
"
);
}
#endif
#ifdef ASSERT_PDIR_SANITY
/**
* sba_dump_pdir_entry - debugging only - print one IOMMU PDIR entry
* @ioc: IO MMU structure which owns the pdir we are interested in.
* @msg: text to print ont the output line.
* @pide: pdir index.
*
* Print one entry of the IO MMU PDIR in human readable form.
*/
static
void
sba_dump_pdir_entry
(
struct
ioc
*
ioc
,
char
*
msg
,
uint
pide
)
{
/* start printing from lowest pde in rval */
u64
*
ptr
=
&
(
ioc
->
pdir_base
[
pide
&
~
(
BITS_PER_LONG
-
1
)]);
unsigned
long
*
rptr
=
(
unsigned
long
*
)
&
(
ioc
->
res_map
[(
pide
>>
3
)
&
~
(
sizeof
(
unsigned
long
)
-
1
)]);
uint
rcnt
;
/* printk(KERN_DEBUG "SBA: %s rp %p bit %d rval 0x%lx\n", */
printk
(
"SBA: %s rp %p bit %d rval 0x%lx
\n
"
,
msg
,
rptr
,
pide
&
(
BITS_PER_LONG
-
1
),
*
rptr
);
rcnt
=
0
;
while
(
rcnt
<
BITS_PER_LONG
)
{
printk
(
"%s %2d %p %016Lx
\n
"
,
(
rcnt
==
(
pide
&
(
BITS_PER_LONG
-
1
)))
?
" -->"
:
" "
,
rcnt
,
ptr
,
*
ptr
);
rcnt
++
;
ptr
++
;
}
printk
(
"%s"
,
msg
);
}
/**
* sba_check_pdir - debugging only - consistency checker
* @ioc: IO MMU structure which owns the pdir we are interested in.
* @msg: text to print ont the output line.
*
* Verify the resource map and pdir state is consistent
*/
static
int
sba_check_pdir
(
struct
ioc
*
ioc
,
char
*
msg
)
{
u64
*
rptr_end
=
(
u64
*
)
&
(
ioc
->
res_map
[
ioc
->
res_size
]);
u64
*
rptr
=
(
u64
*
)
ioc
->
res_map
;
/* resource map ptr */
u64
*
pptr
=
ioc
->
pdir_base
;
/* pdir ptr */
uint
pide
=
0
;
while
(
rptr
<
rptr_end
)
{
u64
rval
;
int
rcnt
;
/* number of bits we might check */
rval
=
*
rptr
;
rcnt
=
64
;
while
(
rcnt
)
{
/* Get last byte and highest bit from that */
u32
pde
=
((
u32
)((
*
pptr
>>
(
63
))
&
0x1
));
if
((
rval
&
0x1
)
^
pde
)
{
/*
** BUMMER! -- res_map != pdir --
** Dump rval and matching pdir entries
*/
sba_dump_pdir_entry
(
ioc
,
msg
,
pide
);
return
(
1
);
}
rcnt
--
;
rval
>>=
1
;
/* try the next bit */
pptr
++
;
pide
++
;
}
rptr
++
;
/* look at next word of res_map */
}
/* It'd be nice if we always got here :^) */
return
0
;
}
/**
* sba_dump_sg - debugging only - print Scatter-Gather list
* @ioc: IO MMU structure which owns the pdir we are interested in.
* @startsg: head of the SG list
* @nents: number of entries in SG list
*
* print the SG list so we can verify it's correct by hand.
*/
static
void
sba_dump_sg
(
struct
ioc
*
ioc
,
struct
scatterlist
*
startsg
,
int
nents
)
{
while
(
nents
--
>
0
)
{
printk
(
" %d : %08lx/%05x %p
\n
"
,
nents
,
(
unsigned
long
)
sba_sg_iova
(
startsg
),
sba_sg_len
(
startsg
),
sba_sg_buffer
(
startsg
));
startsg
++
;
}
}
static
void
sba_check_sg
(
struct
ioc
*
ioc
,
struct
scatterlist
*
startsg
,
int
nents
)
{
struct
scatterlist
*
the_sg
=
startsg
;
int
the_nents
=
nents
;
while
(
the_nents
--
>
0
)
{
if
(
sba_sg_buffer
(
the_sg
)
==
0x0UL
)
sba_dump_sg
(
NULL
,
startsg
,
nents
);
the_sg
++
;
}
}
#endif
/* ASSERT_PDIR_SANITY */
/**************************************************************
*
* I/O Pdir Resource Management
*
* Bits set in the resource map are in use.
* Each bit can represent a number of pages.
* LSbs represent lower addresses (IOVA's).
*
***************************************************************/
#define PAGES_PER_RANGE 1
/* could increase this to 4 or 8 if needed */
/* Convert from IOVP to IOVA and vice versa. */
#define SBA_IOVA(ioc,iovp,offset,hint_reg) ((ioc->ibase) | (iovp) | (offset) | ((hint_reg)<<(ioc->hint_shift_pdir)))
#define SBA_IOVP(ioc,iova) (((iova) & ioc->hint_mask_pdir) & ~(ioc->ibase))
/* FIXME : review these macros to verify correctness and usage */
#define PDIR_INDEX(iovp) ((iovp)>>IOVP_SHIFT)
#define RESMAP_MASK(n) ~(~0UL << (n))
#define RESMAP_IDX_MASK (sizeof(unsigned long) - 1)
/**
* sba_search_bitmap - find free space in IO PDIR resource bitmap
* @ioc: IO MMU structure which owns the pdir we are interested in.
* @bits_wanted: number of entries we need.
*
* Find consecutive free bits in resource bitmap.
* Each bit represents one entry in the IO Pdir.
* Cool perf optimization: search for log2(size) bits at a time.
*/
static
SBA_INLINE
unsigned
long
sba_search_bitmap
(
struct
ioc
*
ioc
,
unsigned
long
bits_wanted
)
{
unsigned
long
*
res_ptr
=
ioc
->
res_hint
;
unsigned
long
*
res_end
=
(
unsigned
long
*
)
&
(
ioc
->
res_map
[
ioc
->
res_size
]);
unsigned
long
pide
=
~
0UL
;
ASSERT
(((
unsigned
long
)
ioc
->
res_hint
&
(
sizeof
(
unsigned
long
)
-
1UL
))
==
0
);
ASSERT
(
res_ptr
<
res_end
);
if
(
bits_wanted
>
(
BITS_PER_LONG
/
2
))
{
/* Search word at a time - no mask needed */
for
(;
res_ptr
<
res_end
;
++
res_ptr
)
{
if
(
*
res_ptr
==
0
)
{
*
res_ptr
=
RESMAP_MASK
(
bits_wanted
);
pide
=
((
unsigned
long
)
res_ptr
-
(
unsigned
long
)
ioc
->
res_map
);
pide
<<=
3
;
/* convert to bit address */
break
;
}
}
/* point to the next word on next pass */
res_ptr
++
;
ioc
->
res_bitshift
=
0
;
}
else
{
/*
** Search the resource bit map on well-aligned values.
** "o" is the alignment.
** We need the alignment to invalidate I/O TLB using
** SBA HW features in the unmap path.
*/
unsigned
long
o
=
1
<<
get_order
(
bits_wanted
<<
PAGE_SHIFT
);
uint
bitshiftcnt
=
ROUNDUP
(
ioc
->
res_bitshift
,
o
);
unsigned
long
mask
;
if
(
bitshiftcnt
>=
BITS_PER_LONG
)
{
bitshiftcnt
=
0
;
res_ptr
++
;
}
mask
=
RESMAP_MASK
(
bits_wanted
)
<<
bitshiftcnt
;
DBG_RES
(
"%s() o %ld %p"
,
__FUNCTION__
,
o
,
res_ptr
);
while
(
res_ptr
<
res_end
)
{
DBG_RES
(
" %p %lx %lx
\n
"
,
res_ptr
,
mask
,
*
res_ptr
);
ASSERT
(
0
!=
mask
);
if
(
0
==
((
*
res_ptr
)
&
mask
))
{
*
res_ptr
|=
mask
;
/* mark resources busy! */
pide
=
((
unsigned
long
)
res_ptr
-
(
unsigned
long
)
ioc
->
res_map
);
pide
<<=
3
;
/* convert to bit address */
pide
+=
bitshiftcnt
;
break
;
}
mask
<<=
o
;
bitshiftcnt
+=
o
;
if
(
0
==
mask
)
{
mask
=
RESMAP_MASK
(
bits_wanted
);
bitshiftcnt
=
0
;
res_ptr
++
;
}
}
/* look in the same word on the next pass */
ioc
->
res_bitshift
=
bitshiftcnt
+
bits_wanted
;
}
/* wrapped ? */
if
(
res_end
<=
res_ptr
)
{
ioc
->
res_hint
=
(
unsigned
long
*
)
ioc
->
res_map
;
ioc
->
res_bitshift
=
0
;
}
else
{
ioc
->
res_hint
=
res_ptr
;
}
return
(
pide
);
}
/**
* sba_alloc_range - find free bits and mark them in IO PDIR resource bitmap
* @ioc: IO MMU structure which owns the pdir we are interested in.
* @size: number of bytes to create a mapping for
*
* Given a size, find consecutive unmarked and then mark those bits in the
* resource bit map.
*/
static
int
sba_alloc_range
(
struct
ioc
*
ioc
,
size_t
size
)
{
unsigned
int
pages_needed
=
size
>>
IOVP_SHIFT
;
#ifdef CONFIG_PROC_FS
unsigned
long
itc_start
=
ia64_get_itc
();
#endif
unsigned
long
pide
;
ASSERT
(
pages_needed
);
ASSERT
((
pages_needed
*
IOVP_SIZE
)
<=
DMA_CHUNK_SIZE
);
ASSERT
(
pages_needed
<=
BITS_PER_LONG
);
ASSERT
(
0
==
(
size
&
~
IOVP_MASK
));
/*
** "seek and ye shall find"...praying never hurts either...
*/
pide
=
sba_search_bitmap
(
ioc
,
pages_needed
);
if
(
pide
>=
(
ioc
->
res_size
<<
3
))
{
pide
=
sba_search_bitmap
(
ioc
,
pages_needed
);
if
(
pide
>=
(
ioc
->
res_size
<<
3
))
panic
(
__FILE__
": I/O MMU @ %lx is out of mapping resources
\n
"
,
ioc
->
ioc_hpa
);
}
#ifdef ASSERT_PDIR_SANITY
/* verify the first enable bit is clear */
if
(
0x00
!=
((
u8
*
)
ioc
->
pdir_base
)[
pide
*
sizeof
(
u64
)
+
7
])
{
sba_dump_pdir_entry
(
ioc
,
"sba_search_bitmap() botched it?"
,
pide
);
}
#endif
DBG_RES
(
"%s(%x) %d -> %lx hint %x/%x
\n
"
,
__FUNCTION__
,
size
,
pages_needed
,
pide
,
(
uint
)
((
unsigned
long
)
ioc
->
res_hint
-
(
unsigned
long
)
ioc
->
res_map
),
ioc
->
res_bitshift
);
#ifdef CONFIG_PROC_FS
{
unsigned
long
itc_end
=
ia64_get_itc
();
unsigned
long
tmp
=
itc_end
-
itc_start
;
/* check for roll over */
itc_start
=
(
itc_end
<
itc_start
)
?
-
(
tmp
)
:
(
tmp
);
}
ioc
->
avg_search
[
ioc
->
avg_idx
++
]
=
itc_start
;
ioc
->
avg_idx
&=
SBA_SEARCH_SAMPLE
-
1
;
ioc
->
used_pages
+=
pages_needed
;
#endif
return
(
pide
);
}
/**
* sba_free_range - unmark bits in IO PDIR resource bitmap
* @ioc: IO MMU structure which owns the pdir we are interested in.
* @iova: IO virtual address which was previously allocated.
* @size: number of bytes to create a mapping for
*
* clear bits in the ioc's resource map
*/
static
SBA_INLINE
void
sba_free_range
(
struct
ioc
*
ioc
,
dma_addr_t
iova
,
size_t
size
)
{
unsigned
long
iovp
=
SBA_IOVP
(
ioc
,
iova
);
unsigned
int
pide
=
PDIR_INDEX
(
iovp
);
unsigned
int
ridx
=
pide
>>
3
;
/* convert bit to byte address */
unsigned
long
*
res_ptr
=
(
unsigned
long
*
)
&
((
ioc
)
->
res_map
[
ridx
&
~
RESMAP_IDX_MASK
]);
int
bits_not_wanted
=
size
>>
IOVP_SHIFT
;
/* 3-bits "bit" address plus 2 (or 3) bits for "byte" == bit in word */
unsigned
long
m
=
RESMAP_MASK
(
bits_not_wanted
)
<<
(
pide
&
(
BITS_PER_LONG
-
1
));
DBG_RES
(
"%s( ,%x,%x) %x/%lx %x %p %lx
\n
"
,
__FUNCTION__
,
(
uint
)
iova
,
size
,
bits_not_wanted
,
m
,
pide
,
res_ptr
,
*
res_ptr
);
#ifdef CONFIG_PROC_FS
ioc
->
used_pages
-=
bits_not_wanted
;
#endif
ASSERT
(
m
!=
0
);
ASSERT
(
bits_not_wanted
);
ASSERT
((
bits_not_wanted
*
IOVP_SIZE
)
<=
DMA_CHUNK_SIZE
);
ASSERT
(
bits_not_wanted
<=
BITS_PER_LONG
);
ASSERT
((
*
res_ptr
&
m
)
==
m
);
/* verify same bits are set */
*
res_ptr
&=
~
m
;
}
/**************************************************************
*
* "Dynamic DMA Mapping" support (aka "Coherent I/O")
*
***************************************************************/
#define SBA_DMA_HINT(ioc, val) ((val) << (ioc)->hint_shift_pdir)
/**
* sba_io_pdir_entry - fill in one IO PDIR entry
* @pdir_ptr: pointer to IO PDIR entry
* @vba: Virtual CPU address of buffer to map
*
* SBA Mapping Routine
*
* Given a virtual address (vba, arg1) sba_io_pdir_entry()
* loads the I/O PDIR entry pointed to by pdir_ptr (arg0).
* Each IO Pdir entry consists of 8 bytes as shown below
* (LSB == bit 0):
*
* 63 40 11 7 0
* +-+---------------------+----------------------------------+----+--------+
* |V| U | PPN[39:12] | U | FF |
* +-+---------------------+----------------------------------+----+--------+
*
* V == Valid Bit
* U == Unused
* PPN == Physical Page Number
*
* The physical address fields are filled with the results of virt_to_phys()
* on the vba.
*/
#if 1
#define sba_io_pdir_entry(pdir_ptr, vba) *pdir_ptr = ((vba & ~0xE000000000000FFFULL) | 0x80000000000000FFULL)
#else
void
SBA_INLINE
sba_io_pdir_entry
(
u64
*
pdir_ptr
,
unsigned
long
vba
)
{
*
pdir_ptr
=
((
vba
&
~
0xE000000000000FFFULL
)
|
0x80000000000000FFULL
);
}
#endif
#ifdef ENABLE_MARK_CLEAN
/**
* Since DMA is i-cache coherent, any (complete) pages that were written via
* DMA can be marked as "clean" so that update_mmu_cache() doesn't have to
* flush them when they get mapped into an executable vm-area.
*/
static
void
mark_clean
(
void
*
addr
,
size_t
size
)
{
unsigned
long
pg_addr
,
end
;
pg_addr
=
PAGE_ALIGN
((
unsigned
long
)
addr
);
end
=
(
unsigned
long
)
addr
+
size
;
while
(
pg_addr
+
PAGE_SIZE
<=
end
)
{
struct
page
*
page
=
virt_to_page
(
pg_addr
);
set_bit
(
PG_arch_1
,
&
page
->
flags
);
pg_addr
+=
PAGE_SIZE
;
}
}
#endif
/**
* sba_mark_invalid - invalidate one or more IO PDIR entries
* @ioc: IO MMU structure which owns the pdir we are interested in.
* @iova: IO Virtual Address mapped earlier
* @byte_cnt: number of bytes this mapping covers.
*
* Marking the IO PDIR entry(ies) as Invalid and invalidate
* corresponding IO TLB entry. The PCOM (Purge Command Register)
* is to purge stale entries in the IO TLB when unmapping entries.
*
* The PCOM register supports purging of multiple pages, with a minium
* of 1 page and a maximum of 2GB. Hardware requires the address be
* aligned to the size of the range being purged. The size of the range
* must be a power of 2. The "Cool perf optimization" in the
* allocation routine helps keep that true.
*/
static
SBA_INLINE
void
sba_mark_invalid
(
struct
ioc
*
ioc
,
dma_addr_t
iova
,
size_t
byte_cnt
)
{
u32
iovp
=
(
u32
)
SBA_IOVP
(
ioc
,
iova
);
int
off
=
PDIR_INDEX
(
iovp
);
/* Must be non-zero and rounded up */
ASSERT
(
byte_cnt
>
0
);
ASSERT
(
0
==
(
byte_cnt
&
~
IOVP_MASK
));
#ifdef ASSERT_PDIR_SANITY
/* Assert first pdir entry is set */
if
(
!
(
ioc
->
pdir_base
[
off
]
>>
60
))
{
sba_dump_pdir_entry
(
ioc
,
"sba_mark_invalid()"
,
PDIR_INDEX
(
iovp
));
}
#endif
if
(
byte_cnt
<=
IOVP_SIZE
)
{
ASSERT
(
off
<
ioc
->
pdir_size
);
iovp
|=
IOVP_SHIFT
;
/* set "size" field for PCOM */
/*
** clear I/O PDIR entry "valid" bit
** Do NOT clear the rest - save it for debugging.
** We should only clear bits that have previously
** been enabled.
*/
ioc
->
pdir_base
[
off
]
&=
~
(
0x80000000000000FFULL
);
}
else
{
u32
t
=
get_order
(
byte_cnt
)
+
PAGE_SHIFT
;
iovp
|=
t
;
ASSERT
(
t
<=
31
);
/* 2GB! Max value of "size" field */
do
{
/* verify this pdir entry is enabled */
ASSERT
(
ioc
->
pdir_base
[
off
]
>>
63
);
/* clear I/O Pdir entry "valid" bit first */
ioc
->
pdir_base
[
off
]
&=
~
(
0x80000000000000FFULL
);
off
++
;
byte_cnt
-=
IOVP_SIZE
;
}
while
(
byte_cnt
>
0
);
}
WRITE_REG
(
iovp
,
ioc
->
ioc_hpa
+
IOC_PCOM
);
}
/**
* sba_map_single - map one buffer and return IOVA for DMA
* @dev: instance of PCI owned by the driver that's asking.
* @addr: driver buffer to map.
* @size: number of bytes to map in driver buffer.
* @direction: R/W or both.
*
* See Documentation/DMA-mapping.txt
*/
dma_addr_t
sba_map_single
(
struct
pci_dev
*
dev
,
void
*
addr
,
size_t
size
,
int
direction
)
{
struct
ioc
*
ioc
;
unsigned
long
flags
;
dma_addr_t
iovp
;
dma_addr_t
offset
;
u64
*
pdir_start
;
int
pide
;
#ifdef ALLOW_IOV_BYPASS
unsigned
long
pci_addr
=
virt_to_phys
(
addr
);
#endif
ioc
=
GET_IOC
(
dev
);
ASSERT
(
ioc
);
#ifdef ALLOW_IOV_BYPASS
/*
** Check if the PCI device can DMA to ptr... if so, just return ptr
*/
if
((
pci_addr
&
~
dev
->
dma_mask
)
==
0
)
{
/*
** Device is bit capable of DMA'ing to the buffer...
** just return the PCI address of ptr
*/
#ifdef CONFIG_PROC_FS
spin_lock_irqsave
(
&
ioc
->
res_lock
,
flags
);
ioc
->
msingle_bypass
++
;
spin_unlock_irqrestore
(
&
ioc
->
res_lock
,
flags
);
#endif
DBG_BYPASS
(
"sba_map_single() bypass mask/addr: 0x%lx/0x%lx
\n
"
,
dev
->
dma_mask
,
pci_addr
);
return
pci_addr
;
}
#endif
ASSERT
(
size
>
0
);
ASSERT
(
size
<=
DMA_CHUNK_SIZE
);
/* save offset bits */
offset
=
((
dma_addr_t
)
(
long
)
addr
)
&
~
IOVP_MASK
;
/* round up to nearest IOVP_SIZE */
size
=
(
size
+
offset
+
~
IOVP_MASK
)
&
IOVP_MASK
;
spin_lock_irqsave
(
&
ioc
->
res_lock
,
flags
);
#ifdef ASSERT_PDIR_SANITY
if
(
sba_check_pdir
(
ioc
,
"Check before sba_map_single()"
))
panic
(
"Sanity check failed"
);
#endif
#ifdef CONFIG_PROC_FS
ioc
->
msingle_calls
++
;
ioc
->
msingle_pages
+=
size
>>
IOVP_SHIFT
;
#endif
pide
=
sba_alloc_range
(
ioc
,
size
);
iovp
=
(
dma_addr_t
)
pide
<<
IOVP_SHIFT
;
DBG_RUN
(
"%s() 0x%p -> 0x%lx
\n
"
,
__FUNCTION__
,
addr
,
(
long
)
iovp
|
offset
);
pdir_start
=
&
(
ioc
->
pdir_base
[
pide
]);
while
(
size
>
0
)
{
ASSERT
(((
u8
*
)
pdir_start
)[
7
]
==
0
);
/* verify availability */
sba_io_pdir_entry
(
pdir_start
,
(
unsigned
long
)
addr
);
DBG_RUN
(
" pdir 0x%p %lx
\n
"
,
pdir_start
,
*
pdir_start
);
addr
+=
IOVP_SIZE
;
size
-=
IOVP_SIZE
;
pdir_start
++
;
}
/* form complete address */
#ifdef ASSERT_PDIR_SANITY
sba_check_pdir
(
ioc
,
"Check after sba_map_single()"
);
#endif
spin_unlock_irqrestore
(
&
ioc
->
res_lock
,
flags
);
return
SBA_IOVA
(
ioc
,
iovp
,
offset
,
DEFAULT_DMA_HINT_REG
);
}
/**
* sba_unmap_single - unmap one IOVA and free resources
* @dev: instance of PCI owned by the driver that's asking.
* @iova: IOVA of driver buffer previously mapped.
* @size: number of bytes mapped in driver buffer.
* @direction: R/W or both.
*
* See Documentation/DMA-mapping.txt
*/
void
sba_unmap_single
(
struct
pci_dev
*
dev
,
dma_addr_t
iova
,
size_t
size
,
int
direction
)
{
struct
ioc
*
ioc
;
#if DELAYED_RESOURCE_CNT > 0
struct
sba_dma_pair
*
d
;
#endif
unsigned
long
flags
;
dma_addr_t
offset
;
ioc
=
GET_IOC
(
dev
);
ASSERT
(
ioc
);
#ifdef ALLOW_IOV_BYPASS
if
((
iova
&
ioc
->
imask
)
!=
ioc
->
ibase
)
{
/*
** Address does not fall w/in IOVA, must be bypassing
*/
#ifdef CONFIG_PROC_FS
spin_lock_irqsave
(
&
ioc
->
res_lock
,
flags
);
ioc
->
usingle_bypass
++
;
spin_unlock_irqrestore
(
&
ioc
->
res_lock
,
flags
);
#endif
DBG_BYPASS
(
"sba_unmap_single() bypass addr: 0x%lx
\n
"
,
iova
);
#ifdef ENABLE_MARK_CLEAN
if
(
direction
==
PCI_DMA_FROMDEVICE
)
{
mark_clean
(
phys_to_virt
(
iova
),
size
);
}
#endif
return
;
}
#endif
offset
=
iova
&
~
IOVP_MASK
;
DBG_RUN
(
"%s() iovp 0x%lx/%x
\n
"
,
__FUNCTION__
,
(
long
)
iova
,
size
);
iova
^=
offset
;
/* clear offset bits */
size
+=
offset
;
size
=
ROUNDUP
(
size
,
IOVP_SIZE
);
spin_lock_irqsave
(
&
ioc
->
res_lock
,
flags
);
#ifdef CONFIG_PROC_FS
ioc
->
usingle_calls
++
;
ioc
->
usingle_pages
+=
size
>>
IOVP_SHIFT
;
#endif
#if DELAYED_RESOURCE_CNT > 0
d
=
&
(
ioc
->
saved
[
ioc
->
saved_cnt
]);
d
->
iova
=
iova
;
d
->
size
=
size
;
if
(
++
(
ioc
->
saved_cnt
)
>=
DELAYED_RESOURCE_CNT
)
{
int
cnt
=
ioc
->
saved_cnt
;
while
(
cnt
--
)
{
sba_mark_invalid
(
ioc
,
d
->
iova
,
d
->
size
);
sba_free_range
(
ioc
,
d
->
iova
,
d
->
size
);
d
--
;
}
ioc
->
saved_cnt
=
0
;
READ_REG
(
ioc
->
ioc_hpa
+
IOC_PCOM
);
/* flush purges */
}
#else
/* DELAYED_RESOURCE_CNT == 0 */
sba_mark_invalid
(
ioc
,
iova
,
size
);
sba_free_range
(
ioc
,
iova
,
size
);
READ_REG
(
ioc
->
ioc_hpa
+
IOC_PCOM
);
/* flush purges */
#endif
/* DELAYED_RESOURCE_CNT == 0 */
#ifdef ENABLE_MARK_CLEAN
if
(
direction
==
PCI_DMA_FROMDEVICE
)
{
u32
iovp
=
(
u32
)
SBA_IOVP
(
ioc
,
iova
);
int
off
=
PDIR_INDEX
(
iovp
);
void
*
addr
;
if
(
size
<=
IOVP_SIZE
)
{
addr
=
phys_to_virt
(
ioc
->
pdir_base
[
off
]
&
~
0xE000000000000FFFULL
);
mark_clean
(
addr
,
size
);
}
else
{
size_t
byte_cnt
=
size
;
do
{
addr
=
phys_to_virt
(
ioc
->
pdir_base
[
off
]
&
~
0xE000000000000FFFULL
);
mark_clean
(
addr
,
min
(
byte_cnt
,
IOVP_SIZE
));
off
++
;
byte_cnt
-=
IOVP_SIZE
;
}
while
(
byte_cnt
>
0
);
}
}
#endif
spin_unlock_irqrestore
(
&
ioc
->
res_lock
,
flags
);
/* XXX REVISIT for 2.5 Linux - need syncdma for zero-copy support.
** For Astro based systems this isn't a big deal WRT performance.
** As long as 2.4 kernels copyin/copyout data from/to userspace,
** we don't need the syncdma. The issue here is I/O MMU cachelines
** are *not* coherent in all cases. May be hwrev dependent.
** Need to investigate more.
asm volatile("syncdma");
*/
}
/**
* sba_alloc_consistent - allocate/map shared mem for DMA
* @hwdev: instance of PCI owned by the driver that's asking.
* @size: number of bytes mapped in driver buffer.
* @dma_handle: IOVA of new buffer.
*
* See Documentation/DMA-mapping.txt
*/
void
*
sba_alloc_consistent
(
struct
pci_dev
*
hwdev
,
size_t
size
,
dma_addr_t
*
dma_handle
)
{
void
*
ret
;
if
(
!
hwdev
)
{
/* only support PCI */
*
dma_handle
=
0
;
return
0
;
}
ret
=
(
void
*
)
__get_free_pages
(
GFP_ATOMIC
,
get_order
(
size
));
if
(
ret
)
{
memset
(
ret
,
0
,
size
);
*
dma_handle
=
sba_map_single
(
hwdev
,
ret
,
size
,
0
);
}
return
ret
;
}
/**
* sba_free_consistent - free/unmap shared mem for DMA
* @hwdev: instance of PCI owned by the driver that's asking.
* @size: number of bytes mapped in driver buffer.
* @vaddr: virtual address IOVA of "consistent" buffer.
* @dma_handler: IO virtual address of "consistent" buffer.
*
* See Documentation/DMA-mapping.txt
*/
void
sba_free_consistent
(
struct
pci_dev
*
hwdev
,
size_t
size
,
void
*
vaddr
,
dma_addr_t
dma_handle
)
{
sba_unmap_single
(
hwdev
,
dma_handle
,
size
,
0
);
free_pages
((
unsigned
long
)
vaddr
,
get_order
(
size
));
}
/*
** Since 0 is a valid pdir_base index value, can't use that
** to determine if a value is valid or not. Use a flag to indicate
** the SG list entry contains a valid pdir index.
*/
#define PIDE_FLAG 0x1UL
#ifdef DEBUG_LARGE_SG_ENTRIES
int
dump_run_sg
=
0
;
#endif
/**
* sba_fill_pdir - write allocated SG entries into IO PDIR
* @ioc: IO MMU structure which owns the pdir we are interested in.
* @startsg: list of IOVA/size pairs
* @nents: number of entries in startsg list
*
* Take preprocessed SG list and write corresponding entries
* in the IO PDIR.
*/
static
SBA_INLINE
int
sba_fill_pdir
(
struct
ioc
*
ioc
,
struct
scatterlist
*
startsg
,
int
nents
)
{
struct
scatterlist
*
dma_sg
=
startsg
;
/* pointer to current DMA */
int
n_mappings
=
0
;
u64
*
pdirp
=
0
;
unsigned
long
dma_offset
=
0
;
dma_sg
--
;
while
(
nents
--
>
0
)
{
int
cnt
=
sba_sg_len
(
startsg
);
sba_sg_len
(
startsg
)
=
0
;
#ifdef DEBUG_LARGE_SG_ENTRIES
if
(
dump_run_sg
)
printk
(
" %2d : %08lx/%05x %p
\n
"
,
nents
,
(
unsigned
long
)
sba_sg_iova
(
startsg
),
cnt
,
sba_sg_buffer
(
startsg
)
);
#else
DBG_RUN_SG
(
" %d : %08lx/%05x %p
\n
"
,
nents
,
(
unsigned
long
)
sba_sg_iova
(
startsg
),
cnt
,
sba_sg_buffer
(
startsg
)
);
#endif
/*
** Look for the start of a new DMA stream
*/
if
((
u64
)
sba_sg_iova
(
startsg
)
&
PIDE_FLAG
)
{
u32
pide
=
(
u64
)
sba_sg_iova
(
startsg
)
&
~
PIDE_FLAG
;
dma_offset
=
(
unsigned
long
)
pide
&
~
IOVP_MASK
;
sba_sg_iova
(
startsg
)
=
0
;
dma_sg
++
;
sba_sg_iova
(
dma_sg
)
=
(
char
*
)(
pide
|
ioc
->
ibase
);
pdirp
=
&
(
ioc
->
pdir_base
[
pide
>>
IOVP_SHIFT
]);
n_mappings
++
;
}
/*
** Look for a VCONTIG chunk
*/
if
(
cnt
)
{
unsigned
long
vaddr
=
(
unsigned
long
)
sba_sg_buffer
(
startsg
);
ASSERT
(
pdirp
);
/* Since multiple Vcontig blocks could make up
** one DMA stream, *add* cnt to dma_len.
*/
sba_sg_len
(
dma_sg
)
+=
cnt
;
cnt
+=
dma_offset
;
dma_offset
=
0
;
/* only want offset on first chunk */
cnt
=
ROUNDUP
(
cnt
,
IOVP_SIZE
);
#ifdef CONFIG_PROC_FS
ioc
->
msg_pages
+=
cnt
>>
IOVP_SHIFT
;
#endif
do
{
sba_io_pdir_entry
(
pdirp
,
vaddr
);
vaddr
+=
IOVP_SIZE
;
cnt
-=
IOVP_SIZE
;
pdirp
++
;
}
while
(
cnt
>
0
);
}
startsg
++
;
}
#ifdef DEBUG_LARGE_SG_ENTRIES
dump_run_sg
=
0
;
#endif
return
(
n_mappings
);
}
/*
** Two address ranges are DMA contiguous *iff* "end of prev" and
** "start of next" are both on a page boundry.
**
** (shift left is a quick trick to mask off upper bits)
*/
#define DMA_CONTIG(__X, __Y) \
(((((unsigned long) __X) | ((unsigned long) __Y)) << (BITS_PER_LONG - PAGE_SHIFT)) == 0UL)
/**
* sba_coalesce_chunks - preprocess the SG list
* @ioc: IO MMU structure which owns the pdir we are interested in.
* @startsg: list of IOVA/size pairs
* @nents: number of entries in startsg list
*
* First pass is to walk the SG list and determine where the breaks are
* in the DMA stream. Allocates PDIR entries but does not fill them.
* Returns the number of DMA chunks.
*
* Doing the fill seperate from the coalescing/allocation keeps the
* code simpler. Future enhancement could make one pass through
* the sglist do both.
*/
static
SBA_INLINE
int
sba_coalesce_chunks
(
struct
ioc
*
ioc
,
struct
scatterlist
*
startsg
,
int
nents
)
{
struct
scatterlist
*
vcontig_sg
;
/* VCONTIG chunk head */
unsigned
long
vcontig_len
;
/* len of VCONTIG chunk */
unsigned
long
vcontig_end
;
struct
scatterlist
*
dma_sg
;
/* next DMA stream head */
unsigned
long
dma_offset
,
dma_len
;
/* start/len of DMA stream */
int
n_mappings
=
0
;
while
(
nents
>
0
)
{
unsigned
long
vaddr
=
(
unsigned
long
)
(
startsg
->
address
);
/*
** Prepare for first/next DMA stream
*/
dma_sg
=
vcontig_sg
=
startsg
;
dma_len
=
vcontig_len
=
vcontig_end
=
sba_sg_len
(
startsg
);
vcontig_end
+=
vaddr
;
dma_offset
=
vaddr
&
~
IOVP_MASK
;
/* PARANOID: clear entries */
sba_sg_buffer
(
startsg
)
=
sba_sg_iova
(
startsg
);
sba_sg_iova
(
startsg
)
=
0
;
sba_sg_len
(
startsg
)
=
0
;
/*
** This loop terminates one iteration "early" since
** it's always looking one "ahead".
*/
while
(
--
nents
>
0
)
{
unsigned
long
vaddr
;
/* tmp */
startsg
++
;
/* catch brokenness in SCSI layer */
ASSERT
(
startsg
->
length
<=
DMA_CHUNK_SIZE
);
/*
** First make sure current dma stream won't
** exceed DMA_CHUNK_SIZE if we coalesce the
** next entry.
*/
if
(((
dma_len
+
dma_offset
+
startsg
->
length
+
~
IOVP_MASK
)
&
IOVP_MASK
)
>
DMA_CHUNK_SIZE
)
break
;
/*
** Then look for virtually contiguous blocks.
**
** append the next transaction?
*/
vaddr
=
(
unsigned
long
)
sba_sg_iova
(
startsg
);
if
(
vcontig_end
==
vaddr
)
{
vcontig_len
+=
sba_sg_len
(
startsg
);
vcontig_end
+=
sba_sg_len
(
startsg
);
dma_len
+=
sba_sg_len
(
startsg
);
sba_sg_buffer
(
startsg
)
=
(
char
*
)
vaddr
;
sba_sg_iova
(
startsg
)
=
0
;
sba_sg_len
(
startsg
)
=
0
;
continue
;
}
#ifdef DEBUG_LARGE_SG_ENTRIES
dump_run_sg
=
(
vcontig_len
>
IOVP_SIZE
);
#endif
/*
** Not virtually contigous.
** Terminate prev chunk.
** Start a new chunk.
**
** Once we start a new VCONTIG chunk, dma_offset
** can't change. And we need the offset from the first
** chunk - not the last one. Ergo Successive chunks
** must start on page boundaries and dove tail
** with it's predecessor.
*/
sba_sg_len
(
vcontig_sg
)
=
vcontig_len
;
vcontig_sg
=
startsg
;
vcontig_len
=
sba_sg_len
(
startsg
);
/*
** 3) do the entries end/start on page boundaries?
** Don't update vcontig_end until we've checked.
*/
if
(
DMA_CONTIG
(
vcontig_end
,
vaddr
))
{
vcontig_end
=
vcontig_len
+
vaddr
;
dma_len
+=
vcontig_len
;
sba_sg_buffer
(
startsg
)
=
(
char
*
)
vaddr
;
sba_sg_iova
(
startsg
)
=
0
;
continue
;
}
else
{
break
;
}
}
/*
** End of DMA Stream
** Terminate last VCONTIG block.
** Allocate space for DMA stream.
*/
sba_sg_len
(
vcontig_sg
)
=
vcontig_len
;
dma_len
=
(
dma_len
+
dma_offset
+
~
IOVP_MASK
)
&
IOVP_MASK
;
ASSERT
(
dma_len
<=
DMA_CHUNK_SIZE
);
sba_sg_iova
(
dma_sg
)
=
(
char
*
)
(
PIDE_FLAG
|
(
sba_alloc_range
(
ioc
,
dma_len
)
<<
IOVP_SHIFT
)
|
dma_offset
);
n_mappings
++
;
}
return
n_mappings
;
}
/**
* sba_map_sg - map Scatter/Gather list
* @dev: instance of PCI owned by the driver that's asking.
* @sglist: array of buffer/length pairs
* @nents: number of entries in list
* @direction: R/W or both.
*
* See Documentation/DMA-mapping.txt
*/
int
sba_map_sg
(
struct
pci_dev
*
dev
,
struct
scatterlist
*
sglist
,
int
nents
,
int
direction
)
{
struct
ioc
*
ioc
;
int
coalesced
,
filled
=
0
;
unsigned
long
flags
;
#ifdef ALLOW_IOV_BYPASS
struct
scatterlist
*
sg
;
#endif
DBG_RUN_SG
(
"%s() START %d entries
\n
"
,
__FUNCTION__
,
nents
);
ioc
=
GET_IOC
(
dev
);
ASSERT
(
ioc
);
#ifdef ALLOW_IOV_BYPASS
if
(
dev
->
dma_mask
>=
ioc
->
dma_mask
)
{
for
(
sg
=
sglist
;
filled
<
nents
;
filled
++
,
sg
++
){
sba_sg_buffer
(
sg
)
=
sba_sg_iova
(
sg
);
sba_sg_iova
(
sg
)
=
(
char
*
)
virt_to_phys
(
sba_sg_buffer
(
sg
));
}
#ifdef CONFIG_PROC_FS
spin_lock_irqsave
(
&
ioc
->
res_lock
,
flags
);
ioc
->
msg_bypass
++
;
spin_unlock_irqrestore
(
&
ioc
->
res_lock
,
flags
);
#endif
return
filled
;
}
#endif
/* Fast path single entry scatterlists. */
if
(
nents
==
1
)
{
sba_sg_buffer
(
sglist
)
=
sba_sg_iova
(
sglist
);
sba_sg_iova
(
sglist
)
=
(
char
*
)
sba_map_single
(
dev
,
sba_sg_buffer
(
sglist
),
sba_sg_len
(
sglist
),
direction
);
#ifdef CONFIG_PROC_FS
/*
** Should probably do some stats counting, but trying to
** be precise quickly starts wasting CPU time.
*/
#endif
return
1
;
}
spin_lock_irqsave
(
&
ioc
->
res_lock
,
flags
);
#ifdef ASSERT_PDIR_SANITY
if
(
sba_check_pdir
(
ioc
,
"Check before sba_map_sg()"
))
{
sba_dump_sg
(
ioc
,
sglist
,
nents
);
panic
(
"Check before sba_map_sg()"
);
}
#endif
#ifdef CONFIG_PROC_FS
ioc
->
msg_calls
++
;
#endif
/*
** First coalesce the chunks and allocate I/O pdir space
**
** If this is one DMA stream, we can properly map using the
** correct virtual address associated with each DMA page.
** w/o this association, we wouldn't have coherent DMA!
** Access to the virtual address is what forces a two pass algorithm.
*/
coalesced
=
sba_coalesce_chunks
(
ioc
,
sglist
,
nents
);
/*
** Program the I/O Pdir
**
** map the virtual addresses to the I/O Pdir
** o dma_address will contain the pdir index
** o dma_len will contain the number of bytes to map
** o address contains the virtual address.
*/
filled
=
sba_fill_pdir
(
ioc
,
sglist
,
nents
);
#ifdef ASSERT_PDIR_SANITY
if
(
sba_check_pdir
(
ioc
,
"Check after sba_map_sg()"
))
{
sba_dump_sg
(
ioc
,
sglist
,
nents
);
panic
(
"Check after sba_map_sg()
\n
"
);
}
#endif
spin_unlock_irqrestore
(
&
ioc
->
res_lock
,
flags
);
ASSERT
(
coalesced
==
filled
);
DBG_RUN_SG
(
"%s() DONE %d mappings
\n
"
,
__FUNCTION__
,
filled
);
return
filled
;
}
/**
* sba_unmap_sg - unmap Scatter/Gather list
* @dev: instance of PCI owned by the driver that's asking.
* @sglist: array of buffer/length pairs
* @nents: number of entries in list
* @direction: R/W or both.
*
* See Documentation/DMA-mapping.txt
*/
void
sba_unmap_sg
(
struct
pci_dev
*
dev
,
struct
scatterlist
*
sglist
,
int
nents
,
int
direction
)
{
struct
ioc
*
ioc
;
#ifdef ASSERT_PDIR_SANITY
unsigned
long
flags
;
#endif
DBG_RUN_SG
(
"%s() START %d entries, %p,%x
\n
"
,
__FUNCTION__
,
nents
,
sba_sg_buffer
(
sglist
),
sglist
->
length
);
ioc
=
GET_IOC
(
dev
);
ASSERT
(
ioc
);
#ifdef CONFIG_PROC_FS
ioc
->
usg_calls
++
;
#endif
#ifdef ASSERT_PDIR_SANITY
spin_lock_irqsave
(
&
ioc
->
res_lock
,
flags
);
sba_check_pdir
(
ioc
,
"Check before sba_unmap_sg()"
);
spin_unlock_irqrestore
(
&
ioc
->
res_lock
,
flags
);
#endif
while
(
sba_sg_len
(
sglist
)
&&
nents
--
)
{
sba_unmap_single
(
dev
,
(
dma_addr_t
)
sba_sg_iova
(
sglist
),
sba_sg_len
(
sglist
),
direction
);
#ifdef CONFIG_PROC_FS
/*
** This leaves inconsistent data in the stats, but we can't
** tell which sg lists were mapped by map_single and which
** were coalesced to a single entry. The stats are fun,
** but speed is more important.
*/
ioc
->
usg_pages
+=
(((
u64
)
sba_sg_iova
(
sglist
)
&
~
IOVP_MASK
)
+
sba_sg_len
(
sglist
)
+
IOVP_SIZE
-
1
)
>>
PAGE_SHIFT
;
#endif
++
sglist
;
}
DBG_RUN_SG
(
"%s() DONE (nents %d)
\n
"
,
__FUNCTION__
,
nents
);
#ifdef ASSERT_PDIR_SANITY
spin_lock_irqsave
(
&
ioc
->
res_lock
,
flags
);
sba_check_pdir
(
ioc
,
"Check after sba_unmap_sg()"
);
spin_unlock_irqrestore
(
&
ioc
->
res_lock
,
flags
);
#endif
}
unsigned
long
sba_dma_address
(
struct
scatterlist
*
sg
)
{
return
((
unsigned
long
)
sba_sg_iova
(
sg
));
}
/**************************************************************
*
* Initialization and claim
*
***************************************************************/
static
void
sba_ioc_init
(
struct
sba_device
*
sba_dev
,
struct
ioc
*
ioc
,
int
ioc_num
)
{
u32
iova_space_size
,
iova_space_mask
;
void
*
pdir_base
;
int
pdir_size
,
iov_order
,
tcnfg
;
/*
** Firmware programs the maximum IOV space size into the imask reg
*/
iova_space_size
=
~
(
READ_REG
(
ioc
->
ioc_hpa
+
IOC_IMASK
)
&
0xFFFFFFFFUL
)
+
1
;
#ifdef CONFIG_IA64_HP_PROTO
if
(
!
iova_space_size
)
iova_space_size
=
GB
(
1
);
#endif
/*
** iov_order is always based on a 1GB IOVA space since we want to
** turn on the other half for AGP GART.
*/
iov_order
=
get_order
(
iova_space_size
>>
(
IOVP_SHIFT
-
PAGE_SHIFT
));
ioc
->
pdir_size
=
pdir_size
=
(
iova_space_size
/
IOVP_SIZE
)
*
sizeof
(
u64
);
DBG_INIT
(
"%s() hpa 0x%lx IOV %dMB (%d bits) PDIR size 0x%0x
\n
"
,
__FUNCTION__
,
ioc
->
ioc_hpa
,
iova_space_size
>>
20
,
iov_order
+
PAGE_SHIFT
,
ioc
->
pdir_size
);
/* FIXME : DMA HINTs not used */
ioc
->
hint_shift_pdir
=
iov_order
+
PAGE_SHIFT
;
ioc
->
hint_mask_pdir
=
~
(
0x3
<<
(
iov_order
+
PAGE_SHIFT
));
ioc
->
pdir_base
=
pdir_base
=
(
void
*
)
__get_free_pages
(
GFP_KERNEL
,
get_order
(
pdir_size
));
if
(
NULL
==
pdir_base
)
{
panic
(
__FILE__
":%s() could not allocate I/O Page Table
\n
"
,
__FUNCTION__
);
}
memset
(
pdir_base
,
0
,
pdir_size
);
DBG_INIT
(
"%s() pdir %p size %x hint_shift_pdir %x hint_mask_pdir %lx
\n
"
,
__FUNCTION__
,
pdir_base
,
pdir_size
,
ioc
->
hint_shift_pdir
,
ioc
->
hint_mask_pdir
);
ASSERT
((((
unsigned
long
)
pdir_base
)
&
PAGE_MASK
)
==
(
unsigned
long
)
pdir_base
);
WRITE_REG
(
virt_to_phys
(
pdir_base
),
ioc
->
ioc_hpa
+
IOC_PDIR_BASE
);
DBG_INIT
(
" base %p
\n
"
,
pdir_base
);
/* build IMASK for IOC and Elroy */
iova_space_mask
=
0xffffffff
;
iova_space_mask
<<=
(
iov_order
+
PAGE_SHIFT
);
#ifdef CONFIG_IA64_HP_PROTO
/*
** REVISIT - this is a kludge, but we won't be supporting anything but
** zx1 2.0 or greater for real. When fw is in shape, ibase will
** be preprogrammed w/ the IOVA hole base and imask will give us
** the size.
*/
if
((
sba_dev
->
hw_rev
&
0xFF
)
<
0x20
)
{
DBG_INIT
(
"%s() Found SBA rev < 2.0, setting IOVA base to 0. This device will not be supported in the future.
\n
"
,
__FUNCTION__
);
ioc
->
ibase
=
0x0
;
}
else
#endif
ioc
->
ibase
=
READ_REG
(
ioc
->
ioc_hpa
+
IOC_IBASE
)
&
0xFFFFFFFEUL
;
ioc
->
imask
=
iova_space_mask
;
/* save it */
DBG_INIT
(
"%s() IOV base 0x%lx mask 0x%0lx
\n
"
,
__FUNCTION__
,
ioc
->
ibase
,
ioc
->
imask
);
/*
** FIXME: Hint registers are programmed with default hint
** values during boot, so hints should be sane even if we
** can't reprogram them the way drivers want.
*/
WRITE_REG
(
ioc
->
imask
,
ioc
->
ioc_hpa
+
IOC_IMASK
);
/*
** Setting the upper bits makes checking for bypass addresses
** a little faster later on.
*/
ioc
->
imask
|=
0xFFFFFFFF00000000UL
;
/* Set I/O PDIR Page size to system page size */
switch
(
PAGE_SHIFT
)
{
case
12
:
/* 4K */
tcnfg
=
0
;
break
;
case
13
:
/* 8K */
tcnfg
=
1
;
break
;
case
14
:
/* 16K */
tcnfg
=
2
;
break
;
case
16
:
/* 64K */
tcnfg
=
3
;
break
;
}
WRITE_REG
(
tcnfg
,
ioc
->
ioc_hpa
+
IOC_TCNFG
);
/*
** Program the IOC's ibase and enable IOVA translation
** Bit zero == enable bit.
*/
WRITE_REG
(
ioc
->
ibase
|
1
,
ioc
->
ioc_hpa
+
IOC_IBASE
);
/*
** Clear I/O TLB of any possible entries.
** (Yes. This is a bit paranoid...but so what)
*/
WRITE_REG
(
0
|
31
,
ioc
->
ioc_hpa
+
IOC_PCOM
);
/*
** If an AGP device is present, only use half of the IOV space
** for PCI DMA. Unfortunately we can't know ahead of time
** whether GART support will actually be used, for now we
** can just key on an AGP device found in the system.
** We program the next pdir index after we stop w/ a key for
** the GART code to handshake on.
*/
if
(
SBA_GET_AGP
(
sba_dev
))
{
DBG_INIT
(
"%s() AGP Device found, reserving 512MB for GART support
\n
"
,
__FUNCTION__
);
ioc
->
pdir_size
/=
2
;
((
u64
*
)
pdir_base
)[
PDIR_INDEX
(
iova_space_size
/
2
)]
=
0x0000badbadc0ffeeULL
;
}
DBG_INIT
(
"%s() DONE
\n
"
,
__FUNCTION__
);
}
/**************************************************************************
**
** SBA initialization code (HW and SW)
**
** o identify SBA chip itself
** o FIXME: initialize DMA hints for reasonable defaults
**
**************************************************************************/
static
void
sba_hw_init
(
struct
sba_device
*
sba_dev
)
{
int
i
;
int
num_ioc
;
u64
dma_mask
;
u32
func_id
;
/*
** Identify the SBA so we can set the dma_mask. We can make a virtual
** dma_mask of the memory subsystem such that devices not implmenting
** a full 64bit mask might still be able to bypass efficiently.
*/
func_id
=
READ_REG
(
sba_dev
->
sba_hpa
+
SBA_FUNC_ID
);
if
(
func_id
==
ZX1_FUNC_ID_VALUE
)
{
dma_mask
=
0xFFFFFFFFFFUL
;
}
else
{
dma_mask
=
0xFFFFFFFFFFFFFFFFUL
;
}
DBG_INIT
(
"%s(): ioc->dma_mask == 0x%lx
\n
"
,
__FUNCTION__
,
dma_mask
);
/*
** Leaving in the multiple ioc code from parisc for the future,
** currently there are no muli-ioc mckinley sbas
*/
sba_dev
->
ioc
[
0
].
ioc_hpa
=
SBA_IOC_OFFSET
;
num_ioc
=
1
;
sba_dev
->
num_ioc
=
num_ioc
;
for
(
i
=
0
;
i
<
num_ioc
;
i
++
)
{
sba_dev
->
ioc
[
i
].
dma_mask
=
dma_mask
;
sba_dev
->
ioc
[
i
].
ioc_hpa
+=
sba_dev
->
sba_hpa
;
sba_ioc_init
(
sba_dev
,
&
(
sba_dev
->
ioc
[
i
]),
i
);
}
}
static
void
sba_common_init
(
struct
sba_device
*
sba_dev
)
{
int
i
;
/* add this one to the head of the list (order doesn't matter)
** This will be useful for debugging - especially if we get coredumps
*/
sba_dev
->
next
=
sba_list
;
sba_list
=
sba_dev
;
sba_count
++
;
for
(
i
=
0
;
i
<
sba_dev
->
num_ioc
;
i
++
)
{
int
res_size
;
/* resource map size dictated by pdir_size */
res_size
=
sba_dev
->
ioc
[
i
].
pdir_size
/
sizeof
(
u64
);
/* entries */
res_size
>>=
3
;
/* convert bit count to byte count */
DBG_INIT
(
"%s() res_size 0x%x
\n
"
,
__FUNCTION__
,
res_size
);
sba_dev
->
ioc
[
i
].
res_size
=
res_size
;
sba_dev
->
ioc
[
i
].
res_map
=
(
char
*
)
__get_free_pages
(
GFP_KERNEL
,
get_order
(
res_size
));
if
(
NULL
==
sba_dev
->
ioc
[
i
].
res_map
)
{
panic
(
__FILE__
":%s() could not allocate resource map
\n
"
,
__FUNCTION__
);
}
memset
(
sba_dev
->
ioc
[
i
].
res_map
,
0
,
res_size
);
/* next available IOVP - circular search */
if
((
sba_dev
->
hw_rev
&
0xFF
)
>=
0x20
)
{
sba_dev
->
ioc
[
i
].
res_hint
=
(
unsigned
long
*
)
sba_dev
->
ioc
[
i
].
res_map
;
}
else
{
u64
reserved_iov
;
/* Yet another 1.x hack */
printk
(
"zx1 1.x: Starting resource hint offset into IOV space to avoid initial zero value IOVA
\n
"
);
sba_dev
->
ioc
[
i
].
res_hint
=
(
unsigned
long
*
)
&
(
sba_dev
->
ioc
[
i
].
res_map
[
L1_CACHE_BYTES
]);
sba_dev
->
ioc
[
i
].
res_map
[
0
]
=
0x1
;
sba_dev
->
ioc
[
i
].
pdir_base
[
0
]
=
0x8000badbadc0ffeeULL
;
for
(
reserved_iov
=
0xA0000
;
reserved_iov
<
0xC0000
;
reserved_iov
+=
IOVP_SIZE
)
{
u64
*
res_ptr
=
sba_dev
->
ioc
[
i
].
res_map
;
int
index
=
PDIR_INDEX
(
reserved_iov
);
int
res_word
;
u64
mask
;
res_word
=
(
int
)(
index
/
BITS_PER_LONG
);
mask
=
0x1UL
<<
(
index
-
(
res_word
*
BITS_PER_LONG
));
res_ptr
[
res_word
]
|=
mask
;
sba_dev
->
ioc
[
i
].
pdir_base
[
PDIR_INDEX
(
reserved_iov
)]
=
(
0x80000000000000FFULL
|
reserved_iov
);
}
}
#ifdef ASSERT_PDIR_SANITY
/* Mark first bit busy - ie no IOVA 0 */
sba_dev
->
ioc
[
i
].
res_map
[
0
]
=
0x1
;
sba_dev
->
ioc
[
i
].
pdir_base
[
0
]
=
0x8000badbadc0ffeeULL
;
#endif
DBG_INIT
(
"%s() %d res_map %x %p
\n
"
,
__FUNCTION__
,
i
,
res_size
,
(
void
*
)
sba_dev
->
ioc
[
i
].
res_map
);
}
sba_dev
->
sba_lock
=
SPIN_LOCK_UNLOCKED
;
}
#ifdef CONFIG_PROC_FS
static
int
sba_proc_info
(
char
*
buf
,
char
**
start
,
off_t
offset
,
int
len
)
{
struct
sba_device
*
sba_dev
=
sba_list
;
struct
ioc
*
ioc
=
&
sba_dev
->
ioc
[
0
];
/* FIXME: Multi-IOC support! */
int
total_pages
=
(
int
)
(
ioc
->
res_size
<<
3
);
/* 8 bits per byte */
unsigned
long
i
=
0
,
avg
=
0
,
min
,
max
;
sprintf
(
buf
,
"%s rev %d.%d
\n
"
,
"Hewlett Packard zx1 SBA"
,
((
sba_dev
->
hw_rev
>>
4
)
&
0xF
),
(
sba_dev
->
hw_rev
&
0xF
)
);
sprintf
(
buf
,
"%sIO PDIR size : %d bytes (%d entries)
\n
"
,
buf
,
(
int
)
((
ioc
->
res_size
<<
3
)
*
sizeof
(
u64
)),
/* 8 bits/byte */
total_pages
);
sprintf
(
buf
,
"%sIO PDIR entries : %ld free %ld used (%d%%)
\n
"
,
buf
,
total_pages
-
ioc
->
used_pages
,
ioc
->
used_pages
,
(
int
)
(
ioc
->
used_pages
*
100
/
total_pages
));
sprintf
(
buf
,
"%sResource bitmap : %d bytes (%d pages)
\n
"
,
buf
,
ioc
->
res_size
,
ioc
->
res_size
<<
3
);
/* 8 bits per byte */
min
=
max
=
ioc
->
avg_search
[
0
];
for
(
i
=
0
;
i
<
SBA_SEARCH_SAMPLE
;
i
++
)
{
avg
+=
ioc
->
avg_search
[
i
];
if
(
ioc
->
avg_search
[
i
]
>
max
)
max
=
ioc
->
avg_search
[
i
];
if
(
ioc
->
avg_search
[
i
]
<
min
)
min
=
ioc
->
avg_search
[
i
];
}
avg
/=
SBA_SEARCH_SAMPLE
;
sprintf
(
buf
,
"%s Bitmap search : %ld/%ld/%ld (min/avg/max CPU Cycles)
\n
"
,
buf
,
min
,
avg
,
max
);
sprintf
(
buf
,
"%spci_map_single(): %12ld calls %12ld pages (avg %d/1000)
\n
"
,
buf
,
ioc
->
msingle_calls
,
ioc
->
msingle_pages
,
(
int
)
((
ioc
->
msingle_pages
*
1000
)
/
ioc
->
msingle_calls
));
#ifdef ALLOW_IOV_BYPASS
sprintf
(
buf
,
"%spci_map_single(): %12ld bypasses
\n
"
,
buf
,
ioc
->
msingle_bypass
);
#endif
sprintf
(
buf
,
"%spci_unmap_single: %12ld calls %12ld pages (avg %d/1000)
\n
"
,
buf
,
ioc
->
usingle_calls
,
ioc
->
usingle_pages
,
(
int
)
((
ioc
->
usingle_pages
*
1000
)
/
ioc
->
usingle_calls
));
#ifdef ALLOW_IOV_BYPASS
sprintf
(
buf
,
"%spci_unmap_single: %12ld bypasses
\n
"
,
buf
,
ioc
->
usingle_bypass
);
#endif
sprintf
(
buf
,
"%spci_map_sg() : %12ld calls %12ld pages (avg %d/1000)
\n
"
,
buf
,
ioc
->
msg_calls
,
ioc
->
msg_pages
,
(
int
)
((
ioc
->
msg_pages
*
1000
)
/
ioc
->
msg_calls
));
#ifdef ALLOW_IOV_BYPASS
sprintf
(
buf
,
"%spci_map_sg() : %12ld bypasses
\n
"
,
buf
,
ioc
->
msg_bypass
);
#endif
sprintf
(
buf
,
"%spci_unmap_sg() : %12ld calls %12ld pages (avg %d/1000)
\n
"
,
buf
,
ioc
->
usg_calls
,
ioc
->
usg_pages
,
(
int
)
((
ioc
->
usg_pages
*
1000
)
/
ioc
->
usg_calls
));
return
strlen
(
buf
);
}
static
int
sba_resource_map
(
char
*
buf
,
char
**
start
,
off_t
offset
,
int
len
)
{
struct
ioc
*
ioc
=
sba_list
->
ioc
;
/* FIXME: Multi-IOC support! */
unsigned
int
*
res_ptr
=
(
unsigned
int
*
)
ioc
->
res_map
;
int
i
;
buf
[
0
]
=
'\0'
;
for
(
i
=
0
;
i
<
(
ioc
->
res_size
/
sizeof
(
unsigned
int
));
++
i
,
++
res_ptr
)
{
if
((
i
&
7
)
==
0
)
strcat
(
buf
,
"
\n
"
);
sprintf
(
buf
,
"%s %08x"
,
buf
,
*
res_ptr
);
}
strcat
(
buf
,
"
\n
"
);
return
strlen
(
buf
);
}
#endif
/*
** Determine if sba should claim this chip (return 0) or not (return 1).
** If so, initialize the chip and tell other partners in crime they
** have work to do.
*/
void
__init
sba_init
(
void
)
{
struct
sba_device
*
sba_dev
;
u32
func_id
,
hw_rev
;
u32
*
func_offset
=
NULL
;
int
i
,
agp_found
=
0
;
static
char
sba_rev
[
6
];
struct
pci_dev
*
device
=
NULL
;
u64
hpa
=
0
;
if
(
!
(
device
=
pci_find_device
(
PCI_VENDOR_ID_HP
,
PCI_DEVICE_ID_HP_ZX1_SBA
,
NULL
)))
return
;
for
(
i
=
0
;
i
<
PCI_NUM_RESOURCES
;
i
++
)
{
if
(
pci_resource_flags
(
device
,
i
)
==
IORESOURCE_MEM
)
{
hpa
=
ioremap
(
pci_resource_start
(
device
,
i
),
pci_resource_len
(
device
,
i
));
break
;
}
}
func_id
=
READ_REG
(
hpa
+
SBA_FUNC_ID
);
if
(
func_id
==
ZX1_FUNC_ID_VALUE
)
{
(
void
)
strcpy
(
sba_rev
,
"zx1"
);
func_offset
=
zx1_func_offsets
;
}
else
{
return
;
}
/* Read HW Rev First */
hw_rev
=
READ_REG
(
hpa
+
SBA_FCLASS
)
&
0xFFUL
;
/*
* Not all revision registers of the chipset are updated on every
* turn. Must scan through all functions looking for the highest rev
*/
if
(
func_offset
)
{
for
(
i
=
0
;
func_offset
[
i
]
!=
-
1
;
i
++
)
{
u32
func_rev
;
func_rev
=
READ_REG
(
hpa
+
SBA_FCLASS
+
func_offset
[
i
])
&
0xFFUL
;
DBG_INIT
(
"%s() func offset: 0x%x rev: 0x%x
\n
"
,
__FUNCTION__
,
func_offset
[
i
],
func_rev
);
if
(
func_rev
>
hw_rev
)
hw_rev
=
func_rev
;
}
}
printk
(
KERN_INFO
"%s found %s %d.%d at %s, HPA 0x%lx
\n
"
,
DRIVER_NAME
,
sba_rev
,
((
hw_rev
>>
4
)
&
0xF
),
(
hw_rev
&
0xF
),
device
->
slot_name
,
hpa
);
if
((
hw_rev
&
0xFF
)
<
0x20
)
{
printk
(
KERN_INFO
"%s WARNING rev 2.0 or greater will be required for IO MMU support in the future
\n
"
,
DRIVER_NAME
);
#ifndef CONFIG_IA64_HP_PROTO
panic
(
"%s: CONFIG_IA64_HP_PROTO MUST be enabled to support SBA rev less than 2.0"
,
DRIVER_NAME
);
#endif
}
sba_dev
=
kmalloc
(
sizeof
(
struct
sba_device
),
GFP_KERNEL
);
if
(
NULL
==
sba_dev
)
{
printk
(
KERN_ERR
DRIVER_NAME
" - couldn't alloc sba_device
\n
"
);
return
;
}
memset
(
sba_dev
,
0
,
sizeof
(
struct
sba_device
));
for
(
i
=
0
;
i
<
MAX_IOC
;
i
++
)
spin_lock_init
(
&
(
sba_dev
->
ioc
[
i
].
res_lock
));
sba_dev
->
hw_rev
=
hw_rev
;
sba_dev
->
sba_hpa
=
hpa
;
/*
* We need to check for an AGP device, if we find one, then only
* use part of the IOVA space for PCI DMA, the rest is for GART.
* REVISIT for multiple IOC.
*/
pci_for_each_dev
(
device
)
agp_found
|=
pci_find_capability
(
device
,
PCI_CAP_ID_AGP
);
if
(
agp_found
&&
reserve_sba_gart
)
SBA_SET_AGP
(
sba_dev
);
sba_hw_init
(
sba_dev
);
sba_common_init
(
sba_dev
);
#ifdef CONFIG_PROC_FS
{
struct
proc_dir_entry
*
proc_mckinley_root
;
proc_mckinley_root
=
proc_mkdir
(
"bus/mckinley"
,
0
);
create_proc_info_entry
(
sba_rev
,
0
,
proc_mckinley_root
,
sba_proc_info
);
create_proc_info_entry
(
"bitmap"
,
0
,
proc_mckinley_root
,
sba_resource_map
);
}
#endif
}
static
int
__init
nosbagart
(
char
*
str
)
{
reserve_sba_gart
=
0
;
return
1
;
}
__setup
(
"nosbagart"
,
nosbagart
);
EXPORT_SYMBOL
(
sba_init
);
EXPORT_SYMBOL
(
sba_map_single
);
EXPORT_SYMBOL
(
sba_unmap_single
);
EXPORT_SYMBOL
(
sba_map_sg
);
EXPORT_SYMBOL
(
sba_unmap_sg
);
EXPORT_SYMBOL
(
sba_dma_address
);
EXPORT_SYMBOL
(
sba_alloc_consistent
);
EXPORT_SYMBOL
(
sba_free_consistent
);
arch/ia64/hp/sim/Makefile
View file @
e19f970b
...
@@ -11,3 +11,16 @@ obj-y := hpsim_console.o hpsim_irq.o hpsim_setup.o
...
@@ -11,3 +11,16 @@ obj-y := hpsim_console.o hpsim_irq.o hpsim_setup.o
obj-$(CONFIG_IA64_GENERIC)
+=
hpsim_machvec.o
obj-$(CONFIG_IA64_GENERIC)
+=
hpsim_machvec.o
include
$(TOPDIR)/Rules.make
include
$(TOPDIR)/Rules.make
#
# ia64/platform/hp/sim/Makefile
#
# Copyright (C) 1999 Silicon Graphics, Inc.
# Copyright (C) Srinivasa Thirumalachar (sprasad@engr.sgi.com)
#
O_TARGET
:=
sim.o
obj-y
:=
hpsim_console.o hpsim_irq.o hpsim_setup.o
obj-$(CONFIG_IA64_GENERIC)
+=
hpsim_machvec.o
include
$(TOPDIR)/Rules.make
arch/ia64/hp/hpsim_console.c
→
arch/ia64/hp/
sim/
hpsim_console.c
View file @
e19f970b
File moved
arch/ia64/hp/hpsim_irq.c
→
arch/ia64/hp/
sim/
hpsim_irq.c
View file @
e19f970b
File moved
arch/ia64/hp/hpsim_machvec.c
→
arch/ia64/hp/
sim/
hpsim_machvec.c
View file @
e19f970b
File moved
arch/ia64/hp/hpsim_setup.c
→
arch/ia64/hp/
sim/
hpsim_setup.c
View file @
e19f970b
File moved
arch/ia64/hp/hpsim_ssc.h
→
arch/ia64/hp/
sim/
hpsim_ssc.h
View file @
e19f970b
File moved
arch/ia64/hp/zx1/Makefile
View file @
e19f970b
...
@@ -11,3 +11,16 @@ obj-y := hpzx1_misc.o
...
@@ -11,3 +11,16 @@ obj-y := hpzx1_misc.o
obj-$(CONFIG_IA64_GENERIC)
+=
hpzx1_machvec.o
obj-$(CONFIG_IA64_GENERIC)
+=
hpzx1_machvec.o
include
$(TOPDIR)/Rules.make
include
$(TOPDIR)/Rules.make
#
# ia64/platform/hp/zx1/Makefile
#
# Copyright (C) 2002 Hewlett Packard
# Copyright (C) Alex Williamson (alex_williamson@hp.com)
#
O_TARGET
:=
zx1.o
obj-y
:=
hpzx1_misc.o
obj-$(CONFIG_IA64_GENERIC)
+=
hpzx1_machvec.o
include
$(TOPDIR)/Rules.make
arch/ia64/hp/zx1/hpzx1_machvec.c
View file @
e19f970b
#define MACHVEC_PLATFORM_NAME hpzx1
#define MACHVEC_PLATFORM_NAME hpzx1
#include <asm/machvec_init.h>
#include <asm/machvec_init.h>
#define MACHVEC_PLATFORM_NAME hpzx1
#include <asm/machvec_init.h>
arch/ia64/hp/zx1/hpzx1_misc.c
View file @
e19f970b
...
@@ -398,3 +398,403 @@ hpzx1_pci_fixup (int phase)
...
@@ -398,3 +398,403 @@ hpzx1_pci_fixup (int phase)
if
(
phase
==
1
)
if
(
phase
==
1
)
sba_init
();
sba_init
();
}
}
/*
* Misc. support for HP zx1 chipset support
*
* Copyright (C) 2002 Hewlett-Packard Co
* Copyright (C) 2002 Alex Williamson <alex_williamson@hp.com>
* Copyright (C) 2002 Bjorn Helgaas <bjorn_helgaas@hp.com>
*/
#include <linux/config.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/acpi.h>
#include <asm/iosapic.h>
#include <asm/efi.h>
#include "../drivers/acpi/include/platform/acgcc.h"
#include "../drivers/acpi/include/actypes.h"
#include "../drivers/acpi/include/acexcep.h"
#include "../drivers/acpi/include/acpixf.h"
#include "../drivers/acpi/include/actbl.h"
#include "../drivers/acpi/include/acconfig.h"
#include "../drivers/acpi/include/acmacros.h"
#include "../drivers/acpi/include/aclocal.h"
#include "../drivers/acpi/include/acobject.h"
#include "../drivers/acpi/include/acstruct.h"
#include "../drivers/acpi/include/acnamesp.h"
#include "../drivers/acpi/include/acutils.h"
#define PFX "hpzx1: "
struct
fake_pci_dev
{
struct
fake_pci_dev
*
next
;
unsigned
char
bus
;
unsigned
int
devfn
;
int
sizing
;
// in middle of BAR sizing operation?
unsigned
long
csr_base
;
unsigned
int
csr_size
;
unsigned
long
mapped_csrs
;
// ioremapped
};
static
struct
fake_pci_dev
*
fake_pci_head
,
**
fake_pci_tail
=
&
fake_pci_head
;
static
struct
pci_ops
orig_pci_ops
;
static
inline
struct
fake_pci_dev
*
fake_pci_find_slot
(
unsigned
char
bus
,
unsigned
int
devfn
)
{
struct
fake_pci_dev
*
dev
;
for
(
dev
=
fake_pci_head
;
dev
;
dev
=
dev
->
next
)
if
(
dev
->
bus
==
bus
&&
dev
->
devfn
==
devfn
)
return
dev
;
return
NULL
;
}
static
struct
fake_pci_dev
*
alloc_fake_pci_dev
(
void
)
{
struct
fake_pci_dev
*
dev
;
dev
=
kmalloc
(
sizeof
(
*
dev
),
GFP_KERNEL
);
if
(
!
dev
)
return
NULL
;
memset
(
dev
,
0
,
sizeof
(
*
dev
));
*
fake_pci_tail
=
dev
;
fake_pci_tail
=
&
dev
->
next
;
return
dev
;
}
#define HP_CFG_RD(sz, bits, name) \
static int hp_cfg_read##sz (struct pci_dev *dev, int where, u##bits *value) \
{ \
struct fake_pci_dev *fake_dev; \
if (!(fake_dev = fake_pci_find_slot(dev->bus->number, dev->devfn))) \
return orig_pci_ops.name(dev, where, value); \
\
switch (where) { \
case PCI_COMMAND: \
*value = read##sz(fake_dev->mapped_csrs + where); \
*value |= PCI_COMMAND_MEMORY;
/* SBA omits this */
\
break; \
case PCI_BASE_ADDRESS_0: \
if (fake_dev->sizing) \
*value = ~(fake_dev->csr_size - 1); \
else \
*value = (fake_dev->csr_base & \
PCI_BASE_ADDRESS_MEM_MASK) | \
PCI_BASE_ADDRESS_SPACE_MEMORY; \
fake_dev->sizing = 0; \
break; \
default: \
*value = read##sz(fake_dev->mapped_csrs + where); \
break; \
} \
return PCIBIOS_SUCCESSFUL; \
}
#define HP_CFG_WR(sz, bits, name) \
static int hp_cfg_write##sz (struct pci_dev *dev, int where, u##bits value) \
{ \
struct fake_pci_dev *fake_dev; \
if (!(fake_dev = fake_pci_find_slot(dev->bus->number, dev->devfn))) \
return orig_pci_ops.name(dev, where, value); \
\
switch (where) { \
case PCI_BASE_ADDRESS_0: \
if (value == ~0) \
fake_dev->sizing = 1; \
break; \
default: \
write##sz(value, fake_dev->mapped_csrs + where); \
break; \
} \
return PCIBIOS_SUCCESSFUL; \
}
HP_CFG_RD
(
b
,
8
,
read_byte
)
HP_CFG_RD
(
w
,
16
,
read_word
)
HP_CFG_RD
(
l
,
32
,
read_dword
)
HP_CFG_WR
(
b
,
8
,
write_byte
)
HP_CFG_WR
(
w
,
16
,
write_word
)
HP_CFG_WR
(
l
,
32
,
write_dword
)
static
struct
pci_ops
hp_pci_conf
=
{
hp_cfg_readb
,
hp_cfg_readw
,
hp_cfg_readl
,
hp_cfg_writeb
,
hp_cfg_writew
,
hp_cfg_writel
,
};
/*
* Assume we'll never have a physical slot higher than 0x10, so we can
* use slots above that for "fake" PCI devices to represent things
* that only show up in the ACPI namespace.
*/
#define HP_MAX_SLOT 0x10
static
struct
fake_pci_dev
*
hpzx1_fake_pci_dev
(
unsigned
long
addr
,
unsigned
int
bus
,
unsigned
int
size
)
{
struct
fake_pci_dev
*
dev
;
int
slot
;
// Note: lspci thinks 0x1f is invalid
for
(
slot
=
0x1e
;
slot
>
HP_MAX_SLOT
;
slot
--
)
{
if
(
!
fake_pci_find_slot
(
bus
,
PCI_DEVFN
(
slot
,
0
)))
break
;
}
if
(
slot
==
HP_MAX_SLOT
)
{
printk
(
KERN_ERR
PFX
"no slot space for device (0x%p) on bus 0x%02x
\n
"
,
(
void
*
)
addr
,
bus
);
return
NULL
;
}
dev
=
alloc_fake_pci_dev
();
if
(
!
dev
)
{
printk
(
KERN_ERR
PFX
"no memory for device (0x%p) on bus 0x%02x
\n
"
,
(
void
*
)
addr
,
bus
);
return
NULL
;
}
dev
->
bus
=
bus
;
dev
->
devfn
=
PCI_DEVFN
(
slot
,
0
);
dev
->
csr_base
=
addr
;
dev
->
csr_size
=
size
;
/*
* Drivers should ioremap what they need, but we have to do
* it here, too, so PCI config accesses work.
*/
dev
->
mapped_csrs
=
ioremap
(
dev
->
csr_base
,
dev
->
csr_size
);
return
dev
;
}
typedef
struct
{
u8
guid_id
;
u8
guid
[
16
];
u8
csr_base
[
8
];
u8
csr_length
[
8
];
}
acpi_hp_vendor_long
;
#define HP_CCSR_LENGTH 0x21
#define HP_CCSR_TYPE 0x2
#define HP_CCSR_GUID \
((efi_guid_t) { 0x69e9adf9, 0x924f, 0xab5f, { 0xf6, 0x4a, 0x24, 0xd2, 0x01, 0x37, 0x0e, 0xad }})
extern
acpi_status
acpi_get_crs
(
acpi_handle
,
acpi_buffer
*
);
extern
acpi_resource
*
acpi_get_crs_next
(
acpi_buffer
*
,
int
*
);
extern
acpi_resource_data
*
acpi_get_crs_type
(
acpi_buffer
*
,
int
*
,
int
);
extern
void
acpi_dispose_crs
(
acpi_buffer
*
);
extern
acpi_status
acpi_cf_evaluate_method
(
acpi_handle
,
UINT8
*
,
NATIVE_UINT
*
);
static
acpi_status
hp_csr_space
(
acpi_handle
obj
,
u64
*
csr_base
,
u64
*
csr_length
)
{
int
i
,
offset
=
0
;
acpi_status
status
;
acpi_buffer
buf
;
acpi_resource_vendor
*
res
;
acpi_hp_vendor_long
*
hp_res
;
efi_guid_t
vendor_guid
;
*
csr_base
=
0
;
*
csr_length
=
0
;
status
=
acpi_get_crs
(
obj
,
&
buf
);
if
(
status
!=
AE_OK
)
{
printk
(
KERN_ERR
PFX
"Unable to get _CRS data on object
\n
"
);
return
status
;
}
res
=
(
acpi_resource_vendor
*
)
acpi_get_crs_type
(
&
buf
,
&
offset
,
ACPI_RSTYPE_VENDOR
);
if
(
!
res
)
{
printk
(
KERN_ERR
PFX
"Failed to find config space for device
\n
"
);
acpi_dispose_crs
(
&
buf
);
return
AE_NOT_FOUND
;
}
hp_res
=
(
acpi_hp_vendor_long
*
)(
res
->
reserved
);
if
(
res
->
length
!=
HP_CCSR_LENGTH
||
hp_res
->
guid_id
!=
HP_CCSR_TYPE
)
{
printk
(
KERN_ERR
PFX
"Unknown Vendor data
\n
"
);
acpi_dispose_crs
(
&
buf
);
return
AE_TYPE
;
/* Revisit error? */
}
memcpy
(
&
vendor_guid
,
hp_res
->
guid
,
sizeof
(
efi_guid_t
));
if
(
efi_guidcmp
(
vendor_guid
,
HP_CCSR_GUID
)
!=
0
)
{
printk
(
KERN_ERR
PFX
"Vendor GUID does not match
\n
"
);
acpi_dispose_crs
(
&
buf
);
return
AE_TYPE
;
/* Revisit error? */
}
for
(
i
=
0
;
i
<
8
;
i
++
)
{
*
csr_base
|=
((
u64
)(
hp_res
->
csr_base
[
i
])
<<
(
i
*
8
));
*
csr_length
|=
((
u64
)(
hp_res
->
csr_length
[
i
])
<<
(
i
*
8
));
}
acpi_dispose_crs
(
&
buf
);
return
AE_OK
;
}
static
acpi_status
hpzx1_sba_probe
(
acpi_handle
obj
,
u32
depth
,
void
*
context
,
void
**
ret
)
{
u64
csr_base
=
0
,
csr_length
=
0
;
char
*
name
=
context
;
struct
fake_pci_dev
*
dev
;
acpi_status
status
;
status
=
hp_csr_space
(
obj
,
&
csr_base
,
&
csr_length
);
if
(
status
!=
AE_OK
)
return
status
;
/*
* Only SBA shows up in ACPI namespace, so its CSR space
* includes both SBA and IOC. Make SBA and IOC show up
* separately in PCI space.
*/
if
((
dev
=
hpzx1_fake_pci_dev
(
csr_base
,
0
,
0x1000
)))
printk
(
KERN_INFO
PFX
"%s SBA at 0x%lx; pci dev %02x:%02x.%d
\n
"
,
name
,
csr_base
,
dev
->
bus
,
PCI_SLOT
(
dev
->
devfn
),
PCI_FUNC
(
dev
->
devfn
));
if
((
dev
=
hpzx1_fake_pci_dev
(
csr_base
+
0x1000
,
0
,
0x1000
)))
printk
(
KERN_INFO
PFX
"%s IOC at 0x%lx; pci dev %02x:%02x.%d
\n
"
,
name
,
csr_base
+
0x1000
,
dev
->
bus
,
PCI_SLOT
(
dev
->
devfn
),
PCI_FUNC
(
dev
->
devfn
));
return
AE_OK
;
}
static
acpi_status
hpzx1_lba_probe
(
acpi_handle
obj
,
u32
depth
,
void
*
context
,
void
**
ret
)
{
acpi_status
status
;
u64
csr_base
=
0
,
csr_length
=
0
;
char
*
name
=
context
;
NATIVE_UINT
busnum
=
0
;
struct
fake_pci_dev
*
dev
;
status
=
hp_csr_space
(
obj
,
&
csr_base
,
&
csr_length
);
if
(
status
!=
AE_OK
)
return
status
;
status
=
acpi_cf_evaluate_method
(
obj
,
METHOD_NAME__BBN
,
&
busnum
);
if
(
ACPI_FAILURE
(
status
))
{
printk
(
KERN_ERR
PFX
"evaluate _BBN fail=0x%x
\n
"
,
status
);
busnum
=
0
;
// no _BBN; stick it on bus 0
}
if
((
dev
=
hpzx1_fake_pci_dev
(
csr_base
,
busnum
,
csr_length
)))
printk
(
KERN_INFO
PFX
"%s LBA at 0x%lx, _BBN 0x%02x; "
"pci dev %02x:%02x.%d
\n
"
,
name
,
csr_base
,
busnum
,
dev
->
bus
,
PCI_SLOT
(
dev
->
devfn
),
PCI_FUNC
(
dev
->
devfn
));
return
AE_OK
;
}
static
void
hpzx1_acpi_dev_init
(
void
)
{
extern
struct
pci_ops
pci_conf
;
/*
* Make fake PCI devices for the following hardware in the
* ACPI namespace. This makes it more convenient for drivers
* because they can claim these devices based on PCI
* information, rather than needing to know about ACPI. The
* 64-bit "HPA" space for this hardware is available as BAR
* 0/1.
*
* HWP0001: Single IOC SBA w/o IOC in namespace
* HWP0002: LBA device
* HWP0003: AGP LBA device
*/
acpi_get_devices
(
"HWP0001"
,
hpzx1_sba_probe
,
"HWP0001"
,
NULL
);
#ifdef CONFIG_IA64_HP_PROTO
if
(
fake_pci_tail
!=
&
fake_pci_head
)
{
#endif
acpi_get_devices
(
"HWP0002"
,
hpzx1_lba_probe
,
"HWP0002"
,
NULL
);
acpi_get_devices
(
"HWP0003"
,
hpzx1_lba_probe
,
"HWP0003"
,
NULL
);
#ifdef CONFIG_IA64_HP_PROTO
}
#define ZX1_FUNC_ID_VALUE (PCI_DEVICE_ID_HP_ZX1_SBA << 16) | PCI_VENDOR_ID_HP
/*
* Early protos don't have bridges in the ACPI namespace, so
* if we didn't find anything, add the things we know are
* there.
*/
if
(
fake_pci_tail
==
&
fake_pci_head
)
{
u64
hpa
,
csr_base
;
struct
fake_pci_dev
*
dev
;
csr_base
=
0xfed00000UL
;
hpa
=
(
u64
)
ioremap
(
csr_base
,
0x1000
);
if
(
__raw_readl
(
hpa
)
==
ZX1_FUNC_ID_VALUE
)
{
if
((
dev
=
hpzx1_fake_pci_dev
(
csr_base
,
0
,
0x1000
)))
printk
(
KERN_INFO
PFX
"HWP0001 SBA at 0x%lx; "
"pci dev %02x:%02x.%d
\n
"
,
csr_base
,
dev
->
bus
,
PCI_SLOT
(
dev
->
devfn
),
PCI_FUNC
(
dev
->
devfn
));
if
((
dev
=
hpzx1_fake_pci_dev
(
csr_base
+
0x1000
,
0
,
0x1000
)))
printk
(
KERN_INFO
PFX
"HWP0001 IOC at 0x%lx; "
"pci dev %02x:%02x.%d
\n
"
,
csr_base
+
0x1000
,
dev
->
bus
,
PCI_SLOT
(
dev
->
devfn
),
PCI_FUNC
(
dev
->
devfn
));
csr_base
=
0xfed24000UL
;
iounmap
(
hpa
);
hpa
=
(
u64
)
ioremap
(
csr_base
,
0x1000
);
if
((
dev
=
hpzx1_fake_pci_dev
(
csr_base
,
0x40
,
0x1000
)))
printk
(
KERN_INFO
PFX
"HWP0003 AGP LBA at "
"0x%lx; pci dev %02x:%02x.%d
\n
"
,
csr_base
,
dev
->
bus
,
PCI_SLOT
(
dev
->
devfn
),
PCI_FUNC
(
dev
->
devfn
));
}
iounmap
(
hpa
);
}
#endif
if
(
fake_pci_tail
==
&
fake_pci_head
)
return
;
/*
* Replace PCI ops, but only if we made fake devices.
*/
orig_pci_ops
=
pci_conf
;
pci_conf
=
hp_pci_conf
;
}
extern
void
sba_init
(
void
);
void
hpzx1_pci_fixup
(
int
phase
)
{
if
(
phase
==
0
)
hpzx1_acpi_dev_init
();
iosapic_pci_fixup
(
phase
);
if
(
phase
==
1
)
sba_init
();
}
arch/ia64/kernel/Makefile
View file @
e19f970b
...
@@ -17,6 +17,7 @@ obj-y := acpi.o entry.o gate.o efi.o efi_stub.o ia64_ksyms.o irq.o irq_ia64.o ir
...
@@ -17,6 +17,7 @@ obj-y := acpi.o entry.o gate.o efi.o efi_stub.o ia64_ksyms.o irq.o irq_ia64.o ir
machvec.o pal.o process.o perfmon.o ptrace.o sal.o semaphore.o setup.o
\
machvec.o pal.o process.o perfmon.o ptrace.o sal.o semaphore.o setup.o
\
signal.o sys_ia64.o traps.o time.o unaligned.o unwind.o
signal.o sys_ia64.o traps.o time.o unaligned.o unwind.o
obj-$(CONFIG_IA64_GENERIC)
+=
iosapic.o
obj-$(CONFIG_IA64_GENERIC)
+=
iosapic.o
obj-$(CONFIG_IA64_HP_ZX1)
+=
iosapic.o
obj-$(CONFIG_IA64_DIG)
+=
iosapic.o
obj-$(CONFIG_IA64_DIG)
+=
iosapic.o
obj-$(CONFIG_IA64_PALINFO)
+=
palinfo.o
obj-$(CONFIG_IA64_PALINFO)
+=
palinfo.o
obj-$(CONFIG_EFI_VARS)
+=
efivars.o
obj-$(CONFIG_EFI_VARS)
+=
efivars.o
...
...
arch/ia64/kernel/acpi.c
View file @
e19f970b
...
@@ -23,6 +23,7 @@
...
@@ -23,6 +23,7 @@
#include <linux/string.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/types.h>
#include <linux/irq.h>
#include <linux/irq.h>
#include <linux/acpi.h>
#ifdef CONFIG_SERIAL_ACPI
#ifdef CONFIG_SERIAL_ACPI
#include <linux/acpi_serial.h>
#include <linux/acpi_serial.h>
#endif
#endif
...
@@ -58,9 +59,34 @@ asm (".weak iosapic_version");
...
@@ -58,9 +59,34 @@ asm (".weak iosapic_version");
const
char
*
const
char
*
acpi_get_sysname
(
void
)
acpi_get_sysname
(
void
)
{
{
/* the following should go away once we have an ACPI parser: */
#ifdef CONFIG_IA64_GENERIC
#ifdef CONFIG_IA64_GENERIC
return
"hpsim"
;
if
(
efi
.
acpi20
)
{
acpi20_rsdp_t
*
rsdp20
;
acpi_xsdt_t
*
xsdt
;
acpi_desc_table_hdr_t
*
hdrp
;
rsdp20
=
efi
.
acpi20
;
if
(
strncmp
(
rsdp20
->
signature
,
ACPI_RSDP_SIG
,
ACPI_RSDP_SIG_LEN
))
{
printk
(
"ACPI 2.0 RSDP signature incorrect, default to DIG compatible
\n
"
);
return
"dig"
;
}
xsdt
=
__va
(
rsdp20
->
xsdt
);
hdrp
=
&
xsdt
->
header
;
if
(
strncmp
(
hdrp
->
signature
,
ACPI_XSDT_SIG
,
ACPI_XSDT_SIG_LEN
))
{
printk
(
"ACPI 2.0 XSDT signature incorrect, default to DIG compatible
\n
"
);
return
"dig"
;
}
if
(
!
strcmp
(
hdrp
->
oem_id
,
"HP"
))
{
printk
(
"Enabling Hewlett Packard zx1 chipset support
\n
"
);
return
"hpzx1"
;
}
}
return
"dig"
;
#else
#else
# if defined (CONFIG_IA64_HP_SIM)
# if defined (CONFIG_IA64_HP_SIM)
return
"hpsim"
;
return
"hpsim"
;
...
@@ -68,13 +94,14 @@ acpi_get_sysname (void)
...
@@ -68,13 +94,14 @@ acpi_get_sysname (void)
return
"sn1"
;
return
"sn1"
;
# elif defined (CONFIG_IA64_SGI_SN2)
# elif defined (CONFIG_IA64_SGI_SN2)
return
"sn2"
;
return
"sn2"
;
# elif defined (CONFIG_IA64_HP_ZX1)
return
"hpzx1"
;
# elif defined (CONFIG_IA64_DIG)
# elif defined (CONFIG_IA64_DIG)
return
"dig"
;
return
"dig"
;
# else
# else
# error Unknown platform. Fix acpi.c.
# error Unknown platform. Fix acpi.c.
# endif
# endif
#endif
#endif
}
}
/*
/*
...
@@ -679,3 +706,74 @@ acpi_parse (acpi_rsdp_t *rsdp)
...
@@ -679,3 +706,74 @@ acpi_parse (acpi_rsdp_t *rsdp)
# endif
/* CONFIG_ACPI */
# endif
/* CONFIG_ACPI */
return
1
;
return
1
;
}
}
#ifdef CONFIG_ACPI
#include "../drivers/acpi/include/platform/acgcc.h"
#include "../drivers/acpi/include/actypes.h"
#include "../drivers/acpi/include/acexcep.h"
#include "../drivers/acpi/include/acpixf.h"
#include "../drivers/acpi/include/actbl.h"
#include "../drivers/acpi/include/acconfig.h"
#include "../drivers/acpi/include/acmacros.h"
#include "../drivers/acpi/include/aclocal.h"
#include "../drivers/acpi/include/acobject.h"
#include "../drivers/acpi/include/acstruct.h"
#include "../drivers/acpi/include/acnamesp.h"
#include "../drivers/acpi/include/acutils.h"
/**
* acpi_get_crs - Return the current resource settings for a device
* obj: A handle for this device
* buf: A buffer to be populated by this call.
*
* Pass a valid handle, typically obtained by walking the namespace and a
* pointer to an allocated buffer, and this function will fill in the buffer
* with a list of acpi_resource structures.
*/
acpi_status
acpi_get_crs
(
acpi_handle
obj
,
acpi_buffer
*
buf
)
{
acpi_status
result
;
buf
->
length
=
0
;
buf
->
pointer
=
NULL
;
result
=
acpi_get_current_resources
(
obj
,
buf
);
if
(
result
!=
AE_BUFFER_OVERFLOW
)
return
result
;
buf
->
pointer
=
kmalloc
(
buf
->
length
,
GFP_KERNEL
);
if
(
!
buf
->
pointer
)
return
-
ENOMEM
;
result
=
acpi_get_current_resources
(
obj
,
buf
);
return
result
;
}
acpi_resource
*
acpi_get_crs_next
(
acpi_buffer
*
buf
,
int
*
offset
)
{
acpi_resource
*
res
;
if
(
*
offset
>=
buf
->
length
)
return
NULL
;
res
=
buf
->
pointer
+
*
offset
;
*
offset
+=
res
->
length
;
return
res
;
}
acpi_resource_data
*
acpi_get_crs_type
(
acpi_buffer
*
buf
,
int
*
offset
,
int
type
)
{
for
(;;)
{
acpi_resource
*
res
=
acpi_get_crs_next
(
buf
,
offset
);
if
(
!
res
)
return
NULL
;
if
(
res
->
id
==
type
)
return
&
res
->
data
;
}
}
void
acpi_dispose_crs
(
acpi_buffer
*
buf
)
{
kfree
(
buf
->
pointer
);
}
#endif
/* CONFIG_ACPI */
arch/ia64/kernel/ia64_ksyms.c
View file @
e19f970b
...
@@ -148,3 +148,10 @@ EXPORT_SYMBOL(efi);
...
@@ -148,3 +148,10 @@ EXPORT_SYMBOL(efi);
#include <linux/proc_fs.h>
#include <linux/proc_fs.h>
extern
struct
proc_dir_entry
*
efi_dir
;
extern
struct
proc_dir_entry
*
efi_dir
;
EXPORT_SYMBOL
(
efi_dir
);
EXPORT_SYMBOL
(
efi_dir
);
#include <asm/machvec.h>
#ifdef CONFIG_IA64_GENERIC
EXPORT_SYMBOL
(
ia64_mv
);
#endif
EXPORT_SYMBOL
(
machvec_noop
);
arch/ia64/kernel/irq.c
View file @
e19f970b
...
@@ -68,6 +68,23 @@
...
@@ -68,6 +68,23 @@
irq_desc_t
_irq_desc
[
NR_IRQS
]
__cacheline_aligned
=
irq_desc_t
_irq_desc
[
NR_IRQS
]
__cacheline_aligned
=
{
[
0
...
NR_IRQS
-
1
]
=
{
IRQ_DISABLED
,
&
no_irq_type
,
NULL
,
0
,
SPIN_LOCK_UNLOCKED
}};
{
[
0
...
NR_IRQS
-
1
]
=
{
IRQ_DISABLED
,
&
no_irq_type
,
NULL
,
0
,
SPIN_LOCK_UNLOCKED
}};
#ifdef CONFIG_IA64_GENERIC
struct
irq_desc
*
__ia64_irq_desc
(
unsigned
int
irq
)
{
return
_irq_desc
+
irq
;
}
ia64_vector
__ia64_irq_to_vector
(
unsigned
int
irq
)
{
return
(
ia64_vector
)
irq
;
}
unsigned
int
__ia64_local_vector_to_irq
(
ia64_vector
vec
)
{
return
(
unsigned
int
)
vec
;
}
#endif
static
void
register_irq_proc
(
unsigned
int
irq
);
static
void
register_irq_proc
(
unsigned
int
irq
);
/*
/*
...
...
arch/ia64/kernel/setup.c
View file @
e19f970b
...
@@ -64,6 +64,8 @@ struct screen_info screen_info;
...
@@ -64,6 +64,8 @@ struct screen_info screen_info;
unsigned
long
ia64_iobase
;
/* virtual address for I/O accesses */
unsigned
long
ia64_iobase
;
/* virtual address for I/O accesses */
unsigned
char
aux_device_present
=
0xaa
;
/* XXX remove this when legacy I/O is gone */
#define COMMAND_LINE_SIZE 512
#define COMMAND_LINE_SIZE 512
char
saved_command_line
[
COMMAND_LINE_SIZE
];
/* used in proc filesystem */
char
saved_command_line
[
COMMAND_LINE_SIZE
];
/* used in proc filesystem */
...
...
arch/ia64/mm/tlb.c
View file @
e19f970b
...
@@ -79,7 +79,7 @@ wrap_mmu_context (struct mm_struct *mm)
...
@@ -79,7 +79,7 @@ wrap_mmu_context (struct mm_struct *mm)
flush_tlb_all
();
flush_tlb_all
();
}
}
static
inline
void
void
ia64_global_tlb_purge
(
unsigned
long
start
,
unsigned
long
end
,
unsigned
long
nbits
)
ia64_global_tlb_purge
(
unsigned
long
start
,
unsigned
long
end
,
unsigned
long
nbits
)
{
{
static
spinlock_t
ptcg_lock
=
SPIN_LOCK_UNLOCKED
;
static
spinlock_t
ptcg_lock
=
SPIN_LOCK_UNLOCKED
;
...
...
drivers/pci/pci.ids
View file @
e19f970b
...
@@ -930,6 +930,9 @@
...
@@ -930,6 +930,9 @@
121a NetServer SMIC Controller
121a NetServer SMIC Controller
121b NetServer Legacy COM Port Decoder
121b NetServer Legacy COM Port Decoder
121c NetServer PCI COM Port Decoder
121c NetServer PCI COM Port Decoder
1229 zx1 System Bus Adapter
122a zx1 I/O Controller
122e zx1 Local Bus Adapter
2910 E2910A
2910 E2910A
2925 E2925A
2925 E2925A
103e Solliday Engineering
103e Solliday Engineering
...
...
include/asm-ia64/hw_irq.h
View file @
e19f970b
...
@@ -88,6 +88,7 @@ hw_resend_irq (struct hw_interrupt_type *h, unsigned int vector)
...
@@ -88,6 +88,7 @@ hw_resend_irq (struct hw_interrupt_type *h, unsigned int vector)
extern
struct
irq_desc
_irq_desc
[
NR_IRQS
];
extern
struct
irq_desc
_irq_desc
[
NR_IRQS
];
#ifndef CONFIG_IA64_GENERIC
static
inline
struct
irq_desc
*
static
inline
struct
irq_desc
*
__ia64_irq_desc
(
unsigned
int
irq
)
__ia64_irq_desc
(
unsigned
int
irq
)
{
{
...
@@ -105,6 +106,7 @@ __ia64_local_vector_to_irq (ia64_vector vec)
...
@@ -105,6 +106,7 @@ __ia64_local_vector_to_irq (ia64_vector vec)
{
{
return
(
unsigned
int
)
vec
;
return
(
unsigned
int
)
vec
;
}
}
#endif
/*
/*
* Next follows the irq descriptor interface. On IA-64, each CPU supports 256 interrupt
* Next follows the irq descriptor interface. On IA-64, each CPU supports 256 interrupt
...
...
include/asm-ia64/machvec.h
View file @
e19f970b
...
@@ -68,6 +68,8 @@ extern void machvec_noop (void);
...
@@ -68,6 +68,8 @@ extern void machvec_noop (void);
# include <asm/machvec_hpsim.h>
# include <asm/machvec_hpsim.h>
# elif defined (CONFIG_IA64_DIG)
# elif defined (CONFIG_IA64_DIG)
# include <asm/machvec_dig.h>
# include <asm/machvec_dig.h>
# elif defined (CONFIG_IA64_HP_ZX1)
# include <asm/machvec_hpzx1.h>
# elif defined (CONFIG_IA64_SGI_SN1)
# elif defined (CONFIG_IA64_SGI_SN1)
# include <asm/machvec_sn1.h>
# include <asm/machvec_sn1.h>
# elif defined (CONFIG_IA64_SGI_SN2)
# elif defined (CONFIG_IA64_SGI_SN2)
...
@@ -123,6 +125,7 @@ struct ia64_machine_vector {
...
@@ -123,6 +125,7 @@ struct ia64_machine_vector {
ia64_mv_cmci_handler_t
*
cmci_handler
;
ia64_mv_cmci_handler_t
*
cmci_handler
;
ia64_mv_log_print_t
*
log_print
;
ia64_mv_log_print_t
*
log_print
;
ia64_mv_send_ipi_t
*
send_ipi
;
ia64_mv_send_ipi_t
*
send_ipi
;
ia64_mv_global_tlb_purge_t
*
global_tlb_purge
;
ia64_mv_pci_dma_init
*
dma_init
;
ia64_mv_pci_dma_init
*
dma_init
;
ia64_mv_pci_alloc_consistent
*
alloc_consistent
;
ia64_mv_pci_alloc_consistent
*
alloc_consistent
;
ia64_mv_pci_free_consistent
*
free_consistent
;
ia64_mv_pci_free_consistent
*
free_consistent
;
...
@@ -149,6 +152,7 @@ struct ia64_machine_vector {
...
@@ -149,6 +152,7 @@ struct ia64_machine_vector {
{ \
{ \
#name, \
#name, \
platform_setup, \
platform_setup, \
platform_cpu_init, \
platform_irq_init, \
platform_irq_init, \
platform_pci_fixup, \
platform_pci_fixup, \
platform_map_nr, \
platform_map_nr, \
...
...
include/asm-ia64/machvec_hpzx1.h
View file @
e19f970b
...
@@ -35,3 +35,40 @@ extern ia64_mv_pci_dma_address sba_dma_address;
...
@@ -35,3 +35,40 @@ extern ia64_mv_pci_dma_address sba_dma_address;
#define platform_pci_dma_address sba_dma_address
#define platform_pci_dma_address sba_dma_address
#endif
/* _ASM_IA64_MACHVEC_HPZX1_h */
#endif
/* _ASM_IA64_MACHVEC_HPZX1_h */
#ifndef _ASM_IA64_MACHVEC_HPZX1_h
#define _ASM_IA64_MACHVEC_HPZX1_h
extern
ia64_mv_setup_t
dig_setup
;
extern
ia64_mv_pci_fixup_t
hpzx1_pci_fixup
;
extern
ia64_mv_map_nr_t
map_nr_dense
;
extern
ia64_mv_pci_alloc_consistent
sba_alloc_consistent
;
extern
ia64_mv_pci_free_consistent
sba_free_consistent
;
extern
ia64_mv_pci_map_single
sba_map_single
;
extern
ia64_mv_pci_unmap_single
sba_unmap_single
;
extern
ia64_mv_pci_map_sg
sba_map_sg
;
extern
ia64_mv_pci_unmap_sg
sba_unmap_sg
;
extern
ia64_mv_pci_dma_address
sba_dma_address
;
/*
* This stuff has dual use!
*
* For a generic kernel, the macros are used to initialize the
* platform's machvec structure. When compiling a non-generic kernel,
* the macros are used directly.
*/
#define platform_name "hpzx1"
#define platform_setup dig_setup
#define platform_pci_fixup hpzx1_pci_fixup
#define platform_map_nr map_nr_dense
#define platform_pci_dma_init ((ia64_mv_pci_dma_init *) machvec_noop)
#define platform_pci_alloc_consistent sba_alloc_consistent
#define platform_pci_free_consistent sba_free_consistent
#define platform_pci_map_single sba_map_single
#define platform_pci_unmap_single sba_unmap_single
#define platform_pci_map_sg sba_map_sg
#define platform_pci_unmap_sg sba_unmap_sg
#define platform_pci_dma_sync_single ((ia64_mv_pci_dma_sync_single *) machvec_noop)
#define platform_pci_dma_sync_sg ((ia64_mv_pci_dma_sync_sg *) machvec_noop)
#define platform_pci_dma_address sba_dma_address
#endif
/* _ASM_IA64_MACHVEC_HPZX1_h */
include/asm-ia64/machvec_init.h
View file @
e19f970b
...
@@ -5,6 +5,11 @@
...
@@ -5,6 +5,11 @@
#include <asm/machvec.h>
#include <asm/machvec.h>
extern
ia64_mv_send_ipi_t
ia64_send_ipi
;
extern
ia64_mv_send_ipi_t
ia64_send_ipi
;
extern
ia64_mv_global_tlb_purge_t
ia64_global_tlb_purge
;
extern
ia64_mv_irq_desc
__ia64_irq_desc
;
extern
ia64_mv_irq_to_vector
__ia64_irq_to_vector
;
extern
ia64_mv_local_vector_to_irq
__ia64_local_vector_to_irq
;
extern
ia64_mv_inb_t
__ia64_inb
;
extern
ia64_mv_inb_t
__ia64_inb
;
extern
ia64_mv_inw_t
__ia64_inw
;
extern
ia64_mv_inw_t
__ia64_inw
;
extern
ia64_mv_inl_t
__ia64_inl
;
extern
ia64_mv_inl_t
__ia64_inl
;
...
...
include/linux/pci_ids.h
View file @
e19f970b
...
@@ -507,6 +507,9 @@
...
@@ -507,6 +507,9 @@
#define PCI_DEVICE_ID_HP_DIVA1 0x1049
#define PCI_DEVICE_ID_HP_DIVA1 0x1049
#define PCI_DEVICE_ID_HP_DIVA2 0x104A
#define PCI_DEVICE_ID_HP_DIVA2 0x104A
#define PCI_DEVICE_ID_HP_SP2_0 0x104B
#define PCI_DEVICE_ID_HP_SP2_0 0x104B
#define PCI_DEVICE_ID_HP_ZX1_SBA 0x1229
#define PCI_DEVICE_ID_HP_ZX1_IOC 0x122a
#define PCI_DEVICE_ID_HP_ZX1_LBA 0x122e
#define PCI_VENDOR_ID_PCTECH 0x1042
#define PCI_VENDOR_ID_PCTECH 0x1042
#define PCI_DEVICE_ID_PCTECH_RZ1000 0x1000
#define PCI_DEVICE_ID_PCTECH_RZ1000 0x1000
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment