mirror of
				https://github.com/qemu/qemu.git
				synced 2025-10-31 04:06:46 +00:00 
			
		
		
		
	Improve LASI emulation
Add Artist graphics Fix main memory allocation Improve LDCW emulation wrt real hw -----BEGIN PGP SIGNATURE----- iQFRBAABCgA7FiEEekgeeIaLTbaoWgXAZN846K9+IV8FAl4vMa8dHHJpY2hhcmQu aGVuZGVyc29uQGxpbmFyby5vcmcACgkQZN846K9+IV/3PwgAmw+Q/rT2kT19vMHd 1XHjK1WJNx4SFRQWxwMsbxDoWyFslUZH5G0z0l7zB1eG7ONEZBttUVyOVnyPH5Q5 DUmfHMvS838lHkLU+OWPbfbwB8WZzfwUwHi3u8ljRBM52RZYf+m69/yMRd8H+PmF bDq3zCviAqvIIvWdSmPEfsx9v4WmrE2aULkKN2aZsHYHzkuHmPWfSYe2dzxTcO3z zDXoscUVmtVk29jpwHV4gM7zl9uk8jyvfeeB2fZ2/EY4qgZ+iHrhtnglfCdCCDr0 G1Q5vugJ70lFkYM2EzpyU+leHUREXN7xqYm5Iycv4neO+aS2FFkNpxCZvPofihHo rUFcOw== =kj86 -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/rth/tags/pull-pa-20200127' into staging Improve LASI emulation Add Artist graphics Fix main memory allocation Improve LDCW emulation wrt real hw # gpg: Signature made Mon 27 Jan 2020 18:53:35 GMT # gpg: using RSA key 7A481E78868B4DB6A85A05C064DF38E8AF7E215F # gpg: issuer "richard.henderson@linaro.org" # gpg: Good signature from "Richard Henderson <richard.henderson@linaro.org>" [full] # Primary key fingerprint: 7A48 1E78 868B 4DB6 A85A 05C0 64DF 38E8 AF7E 215F * remotes/rth/tags/pull-pa-20200127: target/hppa: Allow, but diagnose, LDCW aligned only mod 4 hw/hppa/machine: Map the PDC memory region with higher priority hw/hppa/machine: Restrict the total memory size to 3GB hw/hppa/machine: Correctly check the firmware is in PDC range hppa: Add emulation of Artist graphics seabios-hppa: update to latest version hppa: Switch to tulip NIC by default hppa: add emulation of LASI PS2 controllers ps2: accept 'Set Key Make and Break' commands hppa: Add support for LASI chip with i82596 NIC hw/hppa/dino.c: Improve emulation of Dino PCI chip Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
		
						commit
						4c60e32898
					
				| @ -178,6 +178,8 @@ S: Maintained | ||||
| F: target/hppa/ | ||||
| F: hw/hppa/ | ||||
| F: disas/hppa.c | ||||
| F: hw/net/*i82596* | ||||
| F: include/hw/net/lasi_82596.h | ||||
| 
 | ||||
| LM32 TCG CPUs | ||||
| M: Michael Walle <michael@walle.cc> | ||||
| @ -890,7 +892,7 @@ F: hw/*/etraxfs_*.c | ||||
| 
 | ||||
| HP-PARISC Machines | ||||
| ------------------ | ||||
| Dino | ||||
| HP B160L | ||||
| M: Richard Henderson <rth@twiddle.net> | ||||
| R: Helge Deller <deller@gmx.de> | ||||
| S: Odd Fixes | ||||
|  | ||||
| @ -91,6 +91,10 @@ config TCX | ||||
| config CG3 | ||||
|     bool | ||||
| 
 | ||||
| config ARTIST | ||||
|     bool | ||||
|     select FRAMEBUFFER | ||||
| 
 | ||||
| config VGA | ||||
|     bool | ||||
| 
 | ||||
|  | ||||
| @ -40,6 +40,7 @@ common-obj-$(CONFIG_SM501) += sm501.o | ||||
| common-obj-$(CONFIG_TCX) += tcx.o | ||||
| common-obj-$(CONFIG_CG3) += cg3.o | ||||
| common-obj-$(CONFIG_NEXTCUBE) += next-fb.o | ||||
| common-obj-$(CONFIG_ARTIST) += artist.o | ||||
| 
 | ||||
| obj-$(CONFIG_VGA) += vga.o | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										1454
									
								
								hw/display/artist.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1454
									
								
								hw/display/artist.c
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -142,3 +142,12 @@ sii9022_switch_mode(const char *mode) "mode: %s" | ||||
| # ati.c | ||||
| ati_mm_read(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 " %s -> 0x%"PRIx64 | ||||
| ati_mm_write(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 " %s <- 0x%"PRIx64 | ||||
| 
 | ||||
| # artist.c | ||||
| artist_reg_read(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 "%s -> 0x%"PRIx64 | ||||
| artist_reg_write(unsigned int size, uint64_t addr, const char *name, uint64_t val) "%u 0x%"PRIx64 "%s <- 0x%"PRIx64 | ||||
| artist_vram_read(unsigned int size, uint64_t addr, int posx, int posy, uint64_t val) "%u 0x%"PRIx64 " %ux%u-> 0x%"PRIx64 | ||||
| artist_vram_write(unsigned int size, uint64_t addr, uint64_t val) "%u 0x%"PRIx64 " <- 0x%"PRIx64 | ||||
| artist_fill_window(unsigned int start_x, unsigned int start_y, unsigned int width, unsigned int height, uint32_t op, uint32_t ctlpln) "start=%ux%u length=%ux%u op=0x%08x ctlpln=0x%08x" | ||||
| artist_block_move(unsigned int start_x, unsigned int start_y, unsigned int dest_x, unsigned int dest_y, unsigned int width, unsigned int height) "source %ux%u -> dest %ux%u size %ux%u" | ||||
| artist_draw_line(unsigned int start_x, unsigned int start_y, unsigned int end_x, unsigned int end_y) "%ux%u %ux%u" | ||||
|  | ||||
| @ -10,3 +10,6 @@ config DINO | ||||
|     select IDE_CMD646 | ||||
|     select MC146818RTC | ||||
|     select LSI_SCSI_PCI | ||||
|     select LASI_82596 | ||||
|     select LASIPS2 | ||||
|     select ARTIST | ||||
|  | ||||
| @ -1 +1 @@ | ||||
| obj-$(CONFIG_DINO) += pci.o machine.o dino.o | ||||
| obj-$(CONFIG_DINO) += pci.o machine.o dino.o lasi.o | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| /*
 | ||||
|  * HP-PARISC Dino PCI chipset emulation. | ||||
|  * HP-PARISC Dino PCI chipset emulation, as in B160L and similiar machines | ||||
|  * | ||||
|  * (C) 2017 by Helge Deller <deller@gmx.de> | ||||
|  * (C) 2017-2019 by Helge Deller <deller@gmx.de> | ||||
|  * | ||||
|  * This work is licensed under the GNU GPL license version 2 or later. | ||||
|  * | ||||
| @ -21,6 +21,7 @@ | ||||
| #include "migration/vmstate.h" | ||||
| #include "hppa_sys.h" | ||||
| #include "exec/address-spaces.h" | ||||
| #include "trace.h" | ||||
| 
 | ||||
| 
 | ||||
| #define TYPE_DINO_PCI_HOST_BRIDGE "dino-pcihost" | ||||
| @ -82,11 +83,28 @@ | ||||
| #define DINO_PCI_HOST_BRIDGE(obj) \ | ||||
|     OBJECT_CHECK(DinoState, (obj), TYPE_DINO_PCI_HOST_BRIDGE) | ||||
| 
 | ||||
| #define DINO800_REGS ((DINO_TLTIM - DINO_GMASK) / 4) | ||||
| static const uint32_t reg800_keep_bits[DINO800_REGS] = { | ||||
|             MAKE_64BIT_MASK(0, 1), | ||||
|             MAKE_64BIT_MASK(0, 7), | ||||
|             MAKE_64BIT_MASK(0, 7), | ||||
|             MAKE_64BIT_MASK(0, 8), | ||||
|             MAKE_64BIT_MASK(0, 7), | ||||
|             MAKE_64BIT_MASK(0, 9), | ||||
|             MAKE_64BIT_MASK(0, 32), | ||||
|             MAKE_64BIT_MASK(0, 8), | ||||
|             MAKE_64BIT_MASK(0, 30), | ||||
|             MAKE_64BIT_MASK(0, 25), | ||||
|             MAKE_64BIT_MASK(0, 22), | ||||
|             MAKE_64BIT_MASK(0, 9), | ||||
| }; | ||||
| 
 | ||||
| typedef struct DinoState { | ||||
|     PCIHostState parent_obj; | ||||
| 
 | ||||
|     /* PCI_CONFIG_ADDR is parent_obj.config_reg, via pci_host_conf_be_ops,
 | ||||
|        so that we can map PCI_CONFIG_DATA to pci_host_data_be_ops.  */ | ||||
|     uint32_t config_reg_dino; /* keep original copy, including 2 lowest bits */ | ||||
| 
 | ||||
|     uint32_t iar0; | ||||
|     uint32_t iar1; | ||||
| @ -94,8 +112,12 @@ typedef struct DinoState { | ||||
|     uint32_t ipr; | ||||
|     uint32_t icr; | ||||
|     uint32_t ilr; | ||||
|     uint32_t io_fbb_en; | ||||
|     uint32_t io_addr_en; | ||||
|     uint32_t io_control; | ||||
|     uint32_t toc_addr; | ||||
| 
 | ||||
|     uint32_t reg800[DINO800_REGS]; | ||||
| 
 | ||||
|     MemoryRegion this_mem; | ||||
|     MemoryRegion pci_mem; | ||||
| @ -106,8 +128,6 @@ typedef struct DinoState { | ||||
|     MemoryRegion bm_ram_alias; | ||||
|     MemoryRegion bm_pci_alias; | ||||
|     MemoryRegion bm_cpu_alias; | ||||
| 
 | ||||
|     MemoryRegion cpu0_eir_mem; | ||||
| } DinoState; | ||||
| 
 | ||||
| /*
 | ||||
| @ -122,6 +142,8 @@ static void gsc_to_pci_forwarding(DinoState *s) | ||||
|     tmp = extract32(s->io_control, 7, 2); | ||||
|     enabled = (tmp == 0x01); | ||||
|     io_addr_en = s->io_addr_en; | ||||
|     /* Mask out first (=firmware) and last (=Dino) areas. */ | ||||
|     io_addr_en &= ~(BIT(31) | BIT(0)); | ||||
| 
 | ||||
|     memory_region_transaction_begin(); | ||||
|     for (i = 1; i < 31; i++) { | ||||
| @ -142,6 +164,8 @@ static bool dino_chip_mem_valid(void *opaque, hwaddr addr, | ||||
|                                 unsigned size, bool is_write, | ||||
|                                 MemTxAttrs attrs) | ||||
| { | ||||
|     bool ret = false; | ||||
| 
 | ||||
|     switch (addr) { | ||||
|     case DINO_IAR0: | ||||
|     case DINO_IAR1: | ||||
| @ -152,16 +176,22 @@ static bool dino_chip_mem_valid(void *opaque, hwaddr addr, | ||||
|     case DINO_ICR: | ||||
|     case DINO_ILR: | ||||
|     case DINO_IO_CONTROL: | ||||
|     case DINO_IO_FBB_EN: | ||||
|     case DINO_IO_ADDR_EN: | ||||
|     case DINO_PCI_IO_DATA: | ||||
|         return true; | ||||
|     case DINO_TOC_ADDR: | ||||
|     case DINO_GMASK ... DINO_TLTIM: | ||||
|         ret = true; | ||||
|         break; | ||||
|     case DINO_PCI_IO_DATA + 2: | ||||
|         return size <= 2; | ||||
|         ret = (size <= 2); | ||||
|         break; | ||||
|     case DINO_PCI_IO_DATA + 1: | ||||
|     case DINO_PCI_IO_DATA + 3: | ||||
|         return size == 1; | ||||
|         ret = (size == 1); | ||||
|     } | ||||
|     return false; | ||||
|     trace_dino_chip_mem_valid(addr, ret); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static MemTxResult dino_chip_read_with_attrs(void *opaque, hwaddr addr, | ||||
| @ -194,6 +224,9 @@ static MemTxResult dino_chip_read_with_attrs(void *opaque, hwaddr addr, | ||||
|         } | ||||
|         break; | ||||
| 
 | ||||
|     case DINO_IO_FBB_EN: | ||||
|         val = s->io_fbb_en; | ||||
|         break; | ||||
|     case DINO_IO_ADDR_EN: | ||||
|         val = s->io_addr_en; | ||||
|         break; | ||||
| @ -227,12 +260,28 @@ static MemTxResult dino_chip_read_with_attrs(void *opaque, hwaddr addr, | ||||
|     case DINO_IRR1: | ||||
|         val = s->ilr & s->imr & s->icr; | ||||
|         break; | ||||
|     case DINO_TOC_ADDR: | ||||
|         val = s->toc_addr; | ||||
|         break; | ||||
|     case DINO_GMASK ... DINO_TLTIM: | ||||
|         val = s->reg800[(addr - DINO_GMASK) / 4]; | ||||
|         if (addr == DINO_PAMR) { | ||||
|             val &= ~0x01;  /* LSB is hardwired to 0 */ | ||||
|         } | ||||
|         if (addr == DINO_MLTIM) { | ||||
|             val &= ~0x07;  /* 3 LSB are hardwired to 0 */ | ||||
|         } | ||||
|         if (addr == DINO_BRDG_FEAT) { | ||||
|             val &= ~(0x10710E0ul | 8); /* bits 5-7, 24 & 15 reserved */ | ||||
|         } | ||||
|         break; | ||||
| 
 | ||||
|     default: | ||||
|         /* Controlled by dino_chip_mem_valid above.  */ | ||||
|         g_assert_not_reached(); | ||||
|     } | ||||
| 
 | ||||
|     trace_dino_chip_read(addr, val); | ||||
|     *data = val; | ||||
|     return ret; | ||||
| } | ||||
| @ -245,6 +294,9 @@ static MemTxResult dino_chip_write_with_attrs(void *opaque, hwaddr addr, | ||||
|     AddressSpace *io; | ||||
|     MemTxResult ret; | ||||
|     uint16_t ioaddr; | ||||
|     int i; | ||||
| 
 | ||||
|     trace_dino_chip_write(addr, val); | ||||
| 
 | ||||
|     switch (addr) { | ||||
|     case DINO_IO_DATA ... DINO_PCI_IO_DATA + 3: | ||||
| @ -266,9 +318,11 @@ static MemTxResult dino_chip_write_with_attrs(void *opaque, hwaddr addr, | ||||
|         } | ||||
|         return ret; | ||||
| 
 | ||||
|     case DINO_IO_FBB_EN: | ||||
|         s->io_fbb_en = val & 0x03; | ||||
|         break; | ||||
|     case DINO_IO_ADDR_EN: | ||||
|         /* Never allow first (=firmware) and last (=Dino) areas.  */ | ||||
|         s->io_addr_en = val & 0x7ffffffe; | ||||
|         s->io_addr_en = val; | ||||
|         gsc_to_pci_forwarding(s); | ||||
|         break; | ||||
|     case DINO_IO_CONTROL: | ||||
| @ -292,6 +346,10 @@ static MemTxResult dino_chip_write_with_attrs(void *opaque, hwaddr addr, | ||||
|         /* Any write to IPR clears the register.  */ | ||||
|         s->ipr = 0; | ||||
|         break; | ||||
|     case DINO_TOC_ADDR: | ||||
|         /* IO_COMMAND of CPU with client_id bits */ | ||||
|         s->toc_addr = 0xFFFA0030 | (val & 0x1e000); | ||||
|         break; | ||||
| 
 | ||||
|     case DINO_ILR: | ||||
|     case DINO_IRR0: | ||||
| @ -299,6 +357,12 @@ static MemTxResult dino_chip_write_with_attrs(void *opaque, hwaddr addr, | ||||
|         /* These registers are read-only.  */ | ||||
|         break; | ||||
| 
 | ||||
|     case DINO_GMASK ... DINO_TLTIM: | ||||
|         i = (addr - DINO_GMASK) / 4; | ||||
|         val &= reg800_keep_bits[i]; | ||||
|         s->reg800[i] = val; | ||||
|         break; | ||||
| 
 | ||||
|     default: | ||||
|         /* Controlled by dino_chip_mem_valid above.  */ | ||||
|         g_assert_not_reached(); | ||||
| @ -323,7 +387,7 @@ static const MemoryRegionOps dino_chip_ops = { | ||||
| 
 | ||||
| static const VMStateDescription vmstate_dino = { | ||||
|     .name = "Dino", | ||||
|     .version_id = 1, | ||||
|     .version_id = 2, | ||||
|     .minimum_version_id = 1, | ||||
|     .fields = (VMStateField[]) { | ||||
|         VMSTATE_UINT32(iar0, DinoState), | ||||
| @ -332,13 +396,14 @@ static const VMStateDescription vmstate_dino = { | ||||
|         VMSTATE_UINT32(ipr, DinoState), | ||||
|         VMSTATE_UINT32(icr, DinoState), | ||||
|         VMSTATE_UINT32(ilr, DinoState), | ||||
|         VMSTATE_UINT32(io_fbb_en, DinoState), | ||||
|         VMSTATE_UINT32(io_addr_en, DinoState), | ||||
|         VMSTATE_UINT32(io_control, DinoState), | ||||
|         VMSTATE_UINT32(toc_addr, DinoState), | ||||
|         VMSTATE_END_OF_LIST() | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| /* Unlike pci_config_data_le_ops, no check of high bit set in config_reg.  */ | ||||
| 
 | ||||
| static uint64_t dino_config_data_read(void *opaque, hwaddr addr, unsigned len) | ||||
| @ -362,14 +427,16 @@ static const MemoryRegionOps dino_config_data_ops = { | ||||
| 
 | ||||
| static uint64_t dino_config_addr_read(void *opaque, hwaddr addr, unsigned len) | ||||
| { | ||||
|     PCIHostState *s = opaque; | ||||
|     return s->config_reg; | ||||
|     DinoState *s = opaque; | ||||
|     return s->config_reg_dino; | ||||
| } | ||||
| 
 | ||||
| static void dino_config_addr_write(void *opaque, hwaddr addr, | ||||
|                                    uint64_t val, unsigned len) | ||||
| { | ||||
|     PCIHostState *s = opaque; | ||||
|     DinoState *ds = opaque; | ||||
|     ds->config_reg_dino = val; /* keep a copy of original value */ | ||||
|     s->config_reg = val & ~3U; | ||||
| } | ||||
| 
 | ||||
| @ -453,6 +520,8 @@ PCIBus *dino_init(MemoryRegion *addr_space, | ||||
| 
 | ||||
|     dev = qdev_create(NULL, TYPE_DINO_PCI_HOST_BRIDGE); | ||||
|     s = DINO_PCI_HOST_BRIDGE(dev); | ||||
|     s->iar0 = s->iar1 = CPU_HPA + 3; | ||||
|     s->toc_addr = 0xFFFA0030; /* IO_COMMAND of CPU */ | ||||
| 
 | ||||
|     /* Dino PCI access from main memory.  */ | ||||
|     memory_region_init_io(&s->this_mem, OBJECT(s), &dino_chip_ops, | ||||
|  | ||||
| @ -22,6 +22,7 @@ | ||||
| #define LASI_PS2KBD_HPA 0xffd08000 | ||||
| #define LASI_PS2MOU_HPA 0xffd08100 | ||||
| #define LASI_GFX_HPA    0xf8000000 | ||||
| #define ARTIST_FB_ADDR  0xf9000000 | ||||
| #define CPU_HPA         0xfffb0000 | ||||
| #define MEMORY_HPA      0xfffbf000 | ||||
| 
 | ||||
|  | ||||
| @ -12,6 +12,8 @@ | ||||
| #include "hppa_hardware.h" | ||||
| 
 | ||||
| PCIBus *dino_init(MemoryRegion *, qemu_irq *, qemu_irq *); | ||||
| DeviceState *lasi_init(MemoryRegion *); | ||||
| #define enable_lasi_lan()       0 | ||||
| 
 | ||||
| #define TYPE_DINO_PCI_HOST_BRIDGE "dino-pcihost" | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										368
									
								
								hw/hppa/lasi.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										368
									
								
								hw/hppa/lasi.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,368 @@ | ||||
| /*
 | ||||
|  * HP-PARISC Lasi chipset emulation. | ||||
|  * | ||||
|  * (C) 2019 by Helge Deller <deller@gmx.de> | ||||
|  * | ||||
|  * This work is licensed under the GNU GPL license version 2 or later. | ||||
|  * | ||||
|  * Documentation available at: | ||||
|  * https://parisc.wiki.kernel.org/images-parisc/7/79/Lasi_ers.pdf
 | ||||
|  */ | ||||
| 
 | ||||
| #include "qemu/osdep.h" | ||||
| #include "qemu/units.h" | ||||
| #include "qapi/error.h" | ||||
| #include "cpu.h" | ||||
| #include "trace.h" | ||||
| #include "hw/hw.h" | ||||
| #include "hw/irq.h" | ||||
| #include "sysemu/sysemu.h" | ||||
| #include "sysemu/runstate.h" | ||||
| #include "hppa_sys.h" | ||||
| #include "hw/net/lasi_82596.h" | ||||
| #include "hw/char/parallel.h" | ||||
| #include "hw/char/serial.h" | ||||
| #include "hw/input/lasips2.h" | ||||
| #include "exec/address-spaces.h" | ||||
| #include "migration/vmstate.h" | ||||
| 
 | ||||
| #define TYPE_LASI_CHIP "lasi-chip" | ||||
| 
 | ||||
| #define LASI_IRR        0x00    /* RO */ | ||||
| #define LASI_IMR        0x04 | ||||
| #define LASI_IPR        0x08 | ||||
| #define LASI_ICR        0x0c | ||||
| #define LASI_IAR        0x10 | ||||
| 
 | ||||
| #define LASI_PCR        0x0C000 /* LASI Power Control register */ | ||||
| #define LASI_ERRLOG     0x0C004 /* LASI Error Logging register */ | ||||
| #define LASI_VER        0x0C008 /* LASI Version Control register */ | ||||
| #define LASI_IORESET    0x0C00C /* LASI I/O Reset register */ | ||||
| #define LASI_AMR        0x0C010 /* LASI Arbitration Mask register */ | ||||
| #define LASI_IO_CONF    0x7FFFE /* LASI primary configuration register */ | ||||
| #define LASI_IO_CONF2   0x7FFFF /* LASI secondary configuration register */ | ||||
| 
 | ||||
| #define LASI_BIT(x)     (1ul << (x)) | ||||
| #define LASI_IRQ_BITS   (LASI_BIT(5) | LASI_BIT(7) | LASI_BIT(8) | LASI_BIT(9) \ | ||||
|             | LASI_BIT(13) | LASI_BIT(14) | LASI_BIT(16) | LASI_BIT(17) \ | ||||
|             | LASI_BIT(18) | LASI_BIT(19) | LASI_BIT(20) | LASI_BIT(21) \ | ||||
|             | LASI_BIT(26)) | ||||
| 
 | ||||
| #define ICR_BUS_ERROR_BIT  LASI_BIT(8)  /* bit 8 in ICR */ | ||||
| #define ICR_TOC_BIT        LASI_BIT(1)  /* bit 1 in ICR */ | ||||
| 
 | ||||
| #define LASI_CHIP(obj) \ | ||||
|     OBJECT_CHECK(LasiState, (obj), TYPE_LASI_CHIP) | ||||
| 
 | ||||
| #define LASI_RTC_HPA    (LASI_HPA + 0x9000) | ||||
| 
 | ||||
| typedef struct LasiState { | ||||
|     PCIHostState parent_obj; | ||||
| 
 | ||||
|     uint32_t irr; | ||||
|     uint32_t imr; | ||||
|     uint32_t ipr; | ||||
|     uint32_t icr; | ||||
|     uint32_t iar; | ||||
| 
 | ||||
|     uint32_t errlog; | ||||
|     uint32_t amr; | ||||
|     uint32_t rtc; | ||||
|     time_t rtc_ref; | ||||
| 
 | ||||
|     MemoryRegion this_mem; | ||||
| } LasiState; | ||||
| 
 | ||||
| static bool lasi_chip_mem_valid(void *opaque, hwaddr addr, | ||||
|                                 unsigned size, bool is_write, | ||||
|                                 MemTxAttrs attrs) | ||||
| { | ||||
|     bool ret = false; | ||||
| 
 | ||||
|     switch (addr) { | ||||
|     case LASI_IRR: | ||||
|     case LASI_IMR: | ||||
|     case LASI_IPR: | ||||
|     case LASI_ICR: | ||||
|     case LASI_IAR: | ||||
| 
 | ||||
|     case (LASI_LAN_HPA - LASI_HPA): | ||||
|     case (LASI_LPT_HPA - LASI_HPA): | ||||
|     case (LASI_UART_HPA - LASI_HPA): | ||||
|     case (LASI_RTC_HPA - LASI_HPA): | ||||
| 
 | ||||
|     case LASI_PCR ... LASI_AMR: | ||||
|         ret = true; | ||||
|     } | ||||
| 
 | ||||
|     trace_lasi_chip_mem_valid(addr, ret); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static MemTxResult lasi_chip_read_with_attrs(void *opaque, hwaddr addr, | ||||
|                                              uint64_t *data, unsigned size, | ||||
|                                              MemTxAttrs attrs) | ||||
| { | ||||
|     LasiState *s = opaque; | ||||
|     MemTxResult ret = MEMTX_OK; | ||||
|     uint32_t val; | ||||
| 
 | ||||
|     switch (addr) { | ||||
|     case LASI_IRR: | ||||
|         val = s->irr; | ||||
|         break; | ||||
|     case LASI_IMR: | ||||
|         val = s->imr; | ||||
|         break; | ||||
|     case LASI_IPR: | ||||
|         val = s->ipr; | ||||
|         /* Any read to IPR clears the register.  */ | ||||
|         s->ipr = 0; | ||||
|         break; | ||||
|     case LASI_ICR: | ||||
|         val = s->icr & ICR_BUS_ERROR_BIT; /* bus_error */ | ||||
|         break; | ||||
|     case LASI_IAR: | ||||
|         val = s->iar; | ||||
|         break; | ||||
| 
 | ||||
|     case (LASI_LAN_HPA - LASI_HPA): | ||||
|     case (LASI_LPT_HPA - LASI_HPA): | ||||
|     case (LASI_UART_HPA - LASI_HPA): | ||||
|         val = 0; | ||||
|         break; | ||||
|     case (LASI_RTC_HPA - LASI_HPA): | ||||
|         val = time(NULL); | ||||
|         val += s->rtc_ref; | ||||
|         break; | ||||
| 
 | ||||
|     case LASI_PCR: | ||||
|     case LASI_VER:      /* only version 0 existed. */ | ||||
|     case LASI_IORESET: | ||||
|         val = 0; | ||||
|         break; | ||||
|     case LASI_ERRLOG: | ||||
|         val = s->errlog; | ||||
|         break; | ||||
|     case LASI_AMR: | ||||
|         val = s->amr; | ||||
|         break; | ||||
| 
 | ||||
|     default: | ||||
|         /* Controlled by lasi_chip_mem_valid above. */ | ||||
|         g_assert_not_reached(); | ||||
|     } | ||||
| 
 | ||||
|     trace_lasi_chip_read(addr, val); | ||||
| 
 | ||||
|     *data = val; | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static MemTxResult lasi_chip_write_with_attrs(void *opaque, hwaddr addr, | ||||
|                                               uint64_t val, unsigned size, | ||||
|                                               MemTxAttrs attrs) | ||||
| { | ||||
|     LasiState *s = opaque; | ||||
| 
 | ||||
|     trace_lasi_chip_write(addr, val); | ||||
| 
 | ||||
|     switch (addr) { | ||||
|     case LASI_IRR: | ||||
|         /* read-only.  */ | ||||
|         break; | ||||
|     case LASI_IMR: | ||||
|         s->imr = val;  /* 0x20 ?? */ | ||||
|         assert((val & LASI_IRQ_BITS) == val); | ||||
|         break; | ||||
|     case LASI_IPR: | ||||
|         /* Any write to IPR clears the register. */ | ||||
|         s->ipr = 0; | ||||
|         break; | ||||
|     case LASI_ICR: | ||||
|         s->icr = val; | ||||
|         /* if (val & ICR_TOC_BIT) issue_toc(); */ | ||||
|         break; | ||||
|     case LASI_IAR: | ||||
|         s->iar = val; | ||||
|         break; | ||||
| 
 | ||||
|     case (LASI_LAN_HPA - LASI_HPA): | ||||
|         /* XXX: reset LAN card */ | ||||
|         break; | ||||
|     case (LASI_LPT_HPA - LASI_HPA): | ||||
|         /* XXX: reset parallel port */ | ||||
|         break; | ||||
|     case (LASI_UART_HPA - LASI_HPA): | ||||
|         /* XXX: reset serial port */ | ||||
|         break; | ||||
|     case (LASI_RTC_HPA - LASI_HPA): | ||||
|         s->rtc_ref = val - time(NULL); | ||||
|         break; | ||||
| 
 | ||||
|     case LASI_PCR: | ||||
|         if (val == 0x02) /* immediately power off */ | ||||
|             qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN); | ||||
|         break; | ||||
|     case LASI_ERRLOG: | ||||
|         s->errlog = val; | ||||
|         break; | ||||
|     case LASI_VER: | ||||
|         /* read-only.  */ | ||||
|         break; | ||||
|     case LASI_IORESET: | ||||
|         break;  /* XXX: TODO: Reset various devices. */ | ||||
|     case LASI_AMR: | ||||
|         s->amr = val; | ||||
|         break; | ||||
| 
 | ||||
|     default: | ||||
|         /* Controlled by lasi_chip_mem_valid above. */ | ||||
|         g_assert_not_reached(); | ||||
|     } | ||||
|     return MEMTX_OK; | ||||
| } | ||||
| 
 | ||||
| static const MemoryRegionOps lasi_chip_ops = { | ||||
|     .read_with_attrs = lasi_chip_read_with_attrs, | ||||
|     .write_with_attrs = lasi_chip_write_with_attrs, | ||||
|     .endianness = DEVICE_BIG_ENDIAN, | ||||
|     .valid = { | ||||
|         .min_access_size = 1, | ||||
|         .max_access_size = 4, | ||||
|         .accepts = lasi_chip_mem_valid, | ||||
|     }, | ||||
|     .impl = { | ||||
|         .min_access_size = 1, | ||||
|         .max_access_size = 4, | ||||
|     }, | ||||
| }; | ||||
| 
 | ||||
| static const VMStateDescription vmstate_lasi = { | ||||
|     .name = "Lasi", | ||||
|     .version_id = 1, | ||||
|     .minimum_version_id = 1, | ||||
|     .fields = (VMStateField[]) { | ||||
|         VMSTATE_UINT32(irr, LasiState), | ||||
|         VMSTATE_UINT32(imr, LasiState), | ||||
|         VMSTATE_UINT32(ipr, LasiState), | ||||
|         VMSTATE_UINT32(icr, LasiState), | ||||
|         VMSTATE_UINT32(iar, LasiState), | ||||
|         VMSTATE_UINT32(errlog, LasiState), | ||||
|         VMSTATE_UINT32(amr, LasiState), | ||||
|         VMSTATE_END_OF_LIST() | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| static void lasi_set_irq(void *opaque, int irq, int level) | ||||
| { | ||||
|     LasiState *s = opaque; | ||||
|     uint32_t bit = 1u << irq; | ||||
| 
 | ||||
|     if (level) { | ||||
|         s->ipr |= bit; | ||||
|         if (bit & s->imr) { | ||||
|             uint32_t iar = s->iar; | ||||
|             s->irr |= bit; | ||||
|             if ((s->icr & ICR_BUS_ERROR_BIT) == 0) { | ||||
|                 stl_be_phys(&address_space_memory, iar & -32, iar & 31); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static int lasi_get_irq(unsigned long hpa) | ||||
| { | ||||
|     switch (hpa) { | ||||
|     case LASI_HPA: | ||||
|         return 14; | ||||
|     case LASI_UART_HPA: | ||||
|         return 5; | ||||
|     case LASI_LPT_HPA: | ||||
|         return 7; | ||||
|     case LASI_LAN_HPA: | ||||
|         return 8; | ||||
|     case LASI_SCSI_HPA: | ||||
|         return 9; | ||||
|     case LASI_AUDIO_HPA: | ||||
|         return 13; | ||||
|     case LASI_PS2KBD_HPA: | ||||
|     case LASI_PS2MOU_HPA: | ||||
|         return 26; | ||||
|     default: | ||||
|         g_assert_not_reached(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| DeviceState *lasi_init(MemoryRegion *address_space) | ||||
| { | ||||
|     DeviceState *dev; | ||||
|     LasiState *s; | ||||
| 
 | ||||
|     dev = qdev_create(NULL, TYPE_LASI_CHIP); | ||||
|     s = LASI_CHIP(dev); | ||||
|     s->iar = CPU_HPA + 3; | ||||
| 
 | ||||
|     /* Lasi access from main memory.  */ | ||||
|     memory_region_init_io(&s->this_mem, OBJECT(s), &lasi_chip_ops, | ||||
|                           s, "lasi", 0x100000); | ||||
|     memory_region_add_subregion(address_space, LASI_HPA, &s->this_mem); | ||||
| 
 | ||||
|     qdev_init_nofail(dev); | ||||
| 
 | ||||
|     /* LAN */ | ||||
|     if (enable_lasi_lan()) { | ||||
|         qemu_irq lan_irq = qemu_allocate_irq(lasi_set_irq, s, | ||||
|                 lasi_get_irq(LASI_LAN_HPA)); | ||||
|         lasi_82596_init(address_space, LASI_LAN_HPA, lan_irq); | ||||
|     } | ||||
| 
 | ||||
|     /* Parallel port */ | ||||
|     qemu_irq lpt_irq = qemu_allocate_irq(lasi_set_irq, s, | ||||
|             lasi_get_irq(LASI_LPT_HPA)); | ||||
|     parallel_mm_init(address_space, LASI_LPT_HPA + 0x800, 0, | ||||
|                      lpt_irq, parallel_hds[0]); | ||||
| 
 | ||||
|     /* Real time clock (RTC), it's only one 32-bit counter @9000 */ | ||||
| 
 | ||||
|     s->rtc = time(NULL); | ||||
|     s->rtc_ref = 0; | ||||
| 
 | ||||
|     if (serial_hd(1)) { | ||||
|         /* Serial port */ | ||||
|         qemu_irq serial_irq = qemu_allocate_irq(lasi_set_irq, s, | ||||
|                 lasi_get_irq(LASI_UART_HPA)); | ||||
|         serial_mm_init(address_space, LASI_UART_HPA + 0x800, 0, | ||||
|                 serial_irq, 8000000 / 16, | ||||
|                 serial_hd(0), DEVICE_NATIVE_ENDIAN); | ||||
|     } | ||||
| 
 | ||||
|     /* PS/2 Keyboard/Mouse */ | ||||
|     qemu_irq ps2kbd_irq = qemu_allocate_irq(lasi_set_irq, s, | ||||
|             lasi_get_irq(LASI_PS2KBD_HPA)); | ||||
|     lasips2_init(address_space, LASI_PS2KBD_HPA,  ps2kbd_irq); | ||||
| 
 | ||||
|     return dev; | ||||
| } | ||||
| 
 | ||||
| static void lasi_class_init(ObjectClass *klass, void *data) | ||||
| { | ||||
|     DeviceClass *dc = DEVICE_CLASS(klass); | ||||
| 
 | ||||
|     dc->vmsd = &vmstate_lasi; | ||||
| } | ||||
| 
 | ||||
| static const TypeInfo lasi_pcihost_info = { | ||||
|     .name          = TYPE_LASI_CHIP, | ||||
|     .parent        = TYPE_SYS_BUS_DEVICE, | ||||
|     .instance_size = sizeof(LasiState), | ||||
|     .class_init    = lasi_class_init, | ||||
| }; | ||||
| 
 | ||||
| static void lasi_register_types(void) | ||||
| { | ||||
|     type_register_static(&lasi_pcihost_info); | ||||
| } | ||||
| 
 | ||||
| type_init(lasi_register_types) | ||||
| @ -16,6 +16,7 @@ | ||||
| #include "hw/ide.h" | ||||
| #include "hw/timer/i8254.h" | ||||
| #include "hw/char/serial.h" | ||||
| #include "hw/net/lasi_82596.h" | ||||
| #include "hppa_sys.h" | ||||
| #include "qemu/units.h" | ||||
| #include "qapi/error.h" | ||||
| @ -74,6 +75,7 @@ static void machine_hppa_init(MachineState *machine) | ||||
|     MemoryRegion *cpu_region; | ||||
|     long i; | ||||
|     unsigned int smp_cpus = machine->smp.cpus; | ||||
|     SysBusDevice *s; | ||||
| 
 | ||||
|     ram_size = machine->ram_size; | ||||
| 
 | ||||
| @ -90,16 +92,18 @@ static void machine_hppa_init(MachineState *machine) | ||||
|         g_free(name); | ||||
|     } | ||||
| 
 | ||||
|     /* Limit main memory. */ | ||||
|     if (ram_size > FIRMWARE_START) { | ||||
|         machine->ram_size = ram_size = FIRMWARE_START; | ||||
|     } | ||||
| 
 | ||||
|     /* Main memory region. */ | ||||
|     if (machine->ram_size > 3 * GiB) { | ||||
|         error_report("RAM size is currently restricted to 3GB"); | ||||
|         exit(EXIT_FAILURE); | ||||
|     } | ||||
|     ram_region = g_new(MemoryRegion, 1); | ||||
|     memory_region_allocate_system_memory(ram_region, OBJECT(machine), | ||||
|                                          "ram", ram_size); | ||||
|     memory_region_add_subregion(addr_space, 0, ram_region); | ||||
|     memory_region_add_subregion_overlap(addr_space, 0, ram_region, -1); | ||||
| 
 | ||||
|     /* Init Lasi chip */ | ||||
|     lasi_init(addr_space); | ||||
| 
 | ||||
|     /* Init Dino (PCI host bus chip).  */ | ||||
|     pci_bus = dino_init(addr_space, &rtc_irq, &serial_irq); | ||||
| @ -123,9 +127,20 @@ static void machine_hppa_init(MachineState *machine) | ||||
|     dev = DEVICE(pci_create_simple(pci_bus, -1, "lsi53c895a")); | ||||
|     lsi53c8xx_handle_legacy_cmdline(dev); | ||||
| 
 | ||||
|     /* Network setup.  e1000 is good enough, failing Tulip support.  */ | ||||
|     /* Graphics setup. */ | ||||
|     if (machine->enable_graphics && vga_interface_type != VGA_NONE) { | ||||
|         dev = qdev_create(NULL, "artist"); | ||||
|         qdev_init_nofail(dev); | ||||
|         s = SYS_BUS_DEVICE(dev); | ||||
|         sysbus_mmio_map(s, 0, LASI_GFX_HPA); | ||||
|         sysbus_mmio_map(s, 1, ARTIST_FB_ADDR); | ||||
|     } | ||||
| 
 | ||||
|     /* Network setup. */ | ||||
|     for (i = 0; i < nb_nics; i++) { | ||||
|         pci_nic_init_nofail(&nd_table[i], pci_bus, "e1000", NULL); | ||||
|         if (!enable_lasi_lan()) { | ||||
|             pci_nic_init_nofail(&nd_table[i], pci_bus, "tulip", NULL); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /* Load firmware.  Given that this is not "real" firmware,
 | ||||
| @ -155,7 +170,7 @@ static void machine_hppa_init(MachineState *machine) | ||||
|     qemu_log_mask(CPU_LOG_PAGE, "Firmware loaded at 0x%08" PRIx64 | ||||
|                   "-0x%08" PRIx64 ", entry at 0x%08" PRIx64 ".\n", | ||||
|                   firmware_low, firmware_high, firmware_entry); | ||||
|     if (firmware_low < ram_size || firmware_high >= FIRMWARE_END) { | ||||
|     if (firmware_low < FIRMWARE_START || firmware_high >= FIRMWARE_END) { | ||||
|         error_report("Firmware overlaps with memory or IO space"); | ||||
|         exit(1); | ||||
|     } | ||||
|  | ||||
| @ -2,3 +2,13 @@ | ||||
| 
 | ||||
| # pci.c | ||||
| hppa_pci_iack_write(void) "" | ||||
| 
 | ||||
| # dino.c | ||||
| dino_chip_mem_valid(uint64_t addr, uint32_t val) "access to addr 0x%"PRIx64" is %d" | ||||
| dino_chip_read(uint64_t addr, uint32_t val) "addr 0x%"PRIx64" val 0x%08x" | ||||
| dino_chip_write(uint64_t addr, uint32_t val) "addr 0x%"PRIx64" val 0x%08x" | ||||
| 
 | ||||
| # lasi.c | ||||
| lasi_chip_mem_valid(uint64_t addr, uint32_t val) "access to addr 0x%"PRIx64" is %d" | ||||
| lasi_chip_read(uint64_t addr, uint32_t val) "addr 0x%"PRIx64" val 0x%08x" | ||||
| lasi_chip_write(uint64_t addr, uint32_t val) "addr 0x%"PRIx64" val 0x%08x" | ||||
|  | ||||
| @ -41,3 +41,6 @@ config VHOST_USER_INPUT | ||||
| 
 | ||||
| config TSC210X | ||||
|     bool | ||||
| 
 | ||||
| config LASIPS2 | ||||
|     select PS2 | ||||
|  | ||||
| @ -15,3 +15,4 @@ common-obj-$(CONFIG_VHOST_USER_INPUT) += vhost-user-input.o | ||||
| obj-$(CONFIG_MILKYMIST) += milkymist-softusb.o | ||||
| obj-$(CONFIG_PXA2XX) += pxa2xx_keypad.o | ||||
| obj-$(CONFIG_TSC210X) += tsc210x.o | ||||
| obj-$(CONFIG_LASIPS2) += lasips2.o | ||||
|  | ||||
							
								
								
									
										291
									
								
								hw/input/lasips2.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								hw/input/lasips2.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,291 @@ | ||||
| /*
 | ||||
|  * QEMU HP Lasi PS/2 interface emulation | ||||
|  * | ||||
|  * Copyright (c) 2019 Sven Schnelle | ||||
|  * | ||||
|  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  * of this software and associated documentation files (the "Software"), to deal | ||||
|  * in the Software without restriction, including without limitation the rights | ||||
|  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  * copies of the Software, and to permit persons to whom the Software is | ||||
|  * furnished to do so, subject to the following conditions: | ||||
|  * | ||||
|  * The above copyright notice and this permission notice shall be included in | ||||
|  * all copies or substantial portions of the Software. | ||||
|  * | ||||
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | ||||
|  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
|  * THE SOFTWARE. | ||||
|  */ | ||||
| #include "qemu/osdep.h" | ||||
| #include "qemu/log.h" | ||||
| #include "hw/qdev-properties.h" | ||||
| #include "hw/hw.h" | ||||
| #include "hw/input/ps2.h" | ||||
| #include "hw/input/lasips2.h" | ||||
| #include "hw/sysbus.h" | ||||
| #include "exec/hwaddr.h" | ||||
| #include "sysemu/sysemu.h" | ||||
| #include "trace.h" | ||||
| #include "exec/address-spaces.h" | ||||
| #include "migration/vmstate.h" | ||||
| #include "hw/irq.h" | ||||
| 
 | ||||
| 
 | ||||
| struct LASIPS2State; | ||||
| typedef struct LASIPS2Port { | ||||
|     struct LASIPS2State *parent; | ||||
|     MemoryRegion reg; | ||||
|     void *dev; | ||||
|     uint8_t id; | ||||
|     uint8_t control; | ||||
|     uint8_t buf; | ||||
|     bool loopback_rbne; | ||||
|     bool irq; | ||||
| } LASIPS2Port; | ||||
| 
 | ||||
| typedef struct LASIPS2State { | ||||
|     LASIPS2Port kbd; | ||||
|     LASIPS2Port mouse; | ||||
|     qemu_irq irq; | ||||
| } LASIPS2State; | ||||
| 
 | ||||
| static const VMStateDescription vmstate_lasips2 = { | ||||
|     .name = "lasips2", | ||||
|     .version_id = 0, | ||||
|     .minimum_version_id = 0, | ||||
|     .fields = (VMStateField[]) { | ||||
|         VMSTATE_UINT8(kbd.control, LASIPS2State), | ||||
|         VMSTATE_UINT8(kbd.id, LASIPS2State), | ||||
|         VMSTATE_BOOL(kbd.irq, LASIPS2State), | ||||
|         VMSTATE_UINT8(mouse.control, LASIPS2State), | ||||
|         VMSTATE_UINT8(mouse.id, LASIPS2State), | ||||
|         VMSTATE_BOOL(mouse.irq, LASIPS2State), | ||||
|         VMSTATE_END_OF_LIST() | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| typedef enum { | ||||
|     REG_PS2_ID = 0, | ||||
|     REG_PS2_RCVDATA = 4, | ||||
|     REG_PS2_CONTROL = 8, | ||||
|     REG_PS2_STATUS = 12, | ||||
| } lasips2_read_reg_t; | ||||
| 
 | ||||
| typedef enum { | ||||
|     REG_PS2_RESET = 0, | ||||
|     REG_PS2_XMTDATA = 4, | ||||
| } lasips2_write_reg_t; | ||||
| 
 | ||||
| typedef enum { | ||||
|     LASIPS2_CONTROL_ENABLE = 0x01, | ||||
|     LASIPS2_CONTROL_LOOPBACK = 0x02, | ||||
|     LASIPS2_CONTROL_DIAG = 0x20, | ||||
|     LASIPS2_CONTROL_DATDIR = 0x40, | ||||
|     LASIPS2_CONTROL_CLKDIR = 0x80, | ||||
| } lasips2_control_reg_t; | ||||
| 
 | ||||
| typedef enum { | ||||
|     LASIPS2_STATUS_RBNE = 0x01, | ||||
|     LASIPS2_STATUS_TBNE = 0x02, | ||||
|     LASIPS2_STATUS_TERR = 0x04, | ||||
|     LASIPS2_STATUS_PERR = 0x08, | ||||
|     LASIPS2_STATUS_CMPINTR = 0x10, | ||||
|     LASIPS2_STATUS_DATSHD = 0x40, | ||||
|     LASIPS2_STATUS_CLKSHD = 0x80, | ||||
| } lasips2_status_reg_t; | ||||
| 
 | ||||
| static const char *artist_read_reg_name(uint64_t addr) | ||||
| { | ||||
|     switch (addr & 0xc) { | ||||
|     case REG_PS2_ID: | ||||
|         return " PS2_ID"; | ||||
| 
 | ||||
|     case REG_PS2_RCVDATA: | ||||
|         return " PS2_RCVDATA"; | ||||
| 
 | ||||
|     case REG_PS2_CONTROL: | ||||
|         return " PS2_CONTROL"; | ||||
| 
 | ||||
|     case REG_PS2_STATUS: | ||||
|         return " PS2_STATUS"; | ||||
| 
 | ||||
|     default: | ||||
|         return ""; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static const char *artist_write_reg_name(uint64_t addr) | ||||
| { | ||||
|     switch (addr & 0x0c) { | ||||
|     case REG_PS2_RESET: | ||||
|         return " PS2_RESET"; | ||||
| 
 | ||||
|     case REG_PS2_XMTDATA: | ||||
|         return " PS2_XMTDATA"; | ||||
| 
 | ||||
|     case REG_PS2_CONTROL: | ||||
|         return " PS2_CONTROL"; | ||||
| 
 | ||||
|     default: | ||||
|         return ""; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void lasips2_update_irq(LASIPS2State *s) | ||||
| { | ||||
|     trace_lasips2_intr(s->kbd.irq | s->mouse.irq); | ||||
|     qemu_set_irq(s->irq, s->kbd.irq | s->mouse.irq); | ||||
| } | ||||
| 
 | ||||
| static void lasips2_reg_write(void *opaque, hwaddr addr, uint64_t val, | ||||
|                               unsigned size) | ||||
| { | ||||
|     LASIPS2Port *port = opaque; | ||||
| 
 | ||||
|     trace_lasips2_reg_write(size, port->id, addr, | ||||
|                             artist_write_reg_name(addr), val); | ||||
| 
 | ||||
|     switch (addr & 0xc) { | ||||
|     case REG_PS2_CONTROL: | ||||
|         port->control = val; | ||||
|         break; | ||||
| 
 | ||||
|     case REG_PS2_XMTDATA: | ||||
|         if (port->control & LASIPS2_CONTROL_LOOPBACK) { | ||||
|             port->buf = val; | ||||
|             port->irq = true; | ||||
|             port->loopback_rbne = true; | ||||
|             lasips2_update_irq(port->parent); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         if (port->id) { | ||||
|             ps2_write_mouse(port->dev, val); | ||||
|         } else { | ||||
|             ps2_write_keyboard(port->dev, val); | ||||
|         } | ||||
|         break; | ||||
| 
 | ||||
|     case REG_PS2_RESET: | ||||
|         break; | ||||
| 
 | ||||
|     default: | ||||
|         qemu_log_mask(LOG_UNIMP, "%s: unknown register 0x%02" HWADDR_PRIx "\n", | ||||
|                       __func__, addr); | ||||
|         break; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static uint64_t lasips2_reg_read(void *opaque, hwaddr addr, unsigned size) | ||||
| { | ||||
|     LASIPS2Port *port = opaque; | ||||
|     uint64_t ret = 0; | ||||
| 
 | ||||
|     switch (addr & 0xc) { | ||||
|     case REG_PS2_ID: | ||||
|         ret = port->id; | ||||
|         break; | ||||
| 
 | ||||
|     case REG_PS2_RCVDATA: | ||||
|         if (port->control & LASIPS2_CONTROL_LOOPBACK) { | ||||
|             port->irq = false; | ||||
|             port->loopback_rbne = false; | ||||
|             lasips2_update_irq(port->parent); | ||||
|             ret = port->buf; | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         ret = ps2_read_data(port->dev); | ||||
|         break; | ||||
| 
 | ||||
|     case REG_PS2_CONTROL: | ||||
|         ret = port->control; | ||||
|         break; | ||||
| 
 | ||||
|     case REG_PS2_STATUS: | ||||
| 
 | ||||
|         ret = LASIPS2_STATUS_DATSHD | LASIPS2_STATUS_CLKSHD; | ||||
| 
 | ||||
|         if (port->control & LASIPS2_CONTROL_DIAG) { | ||||
|             if (!(port->control & LASIPS2_CONTROL_DATDIR)) { | ||||
|                 ret &= ~LASIPS2_STATUS_DATSHD; | ||||
|             } | ||||
| 
 | ||||
|             if (!(port->control & LASIPS2_CONTROL_CLKDIR)) { | ||||
|                 ret &= ~LASIPS2_STATUS_CLKSHD; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (port->control & LASIPS2_CONTROL_LOOPBACK) { | ||||
|             if (port->loopback_rbne) { | ||||
|                 ret |= LASIPS2_STATUS_RBNE; | ||||
|             } | ||||
|         } else { | ||||
|             if (!ps2_queue_empty(port->dev)) { | ||||
|                 ret |= LASIPS2_STATUS_RBNE; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (port->parent->kbd.irq || port->parent->mouse.irq) { | ||||
|             ret |= LASIPS2_STATUS_CMPINTR; | ||||
|         } | ||||
|         break; | ||||
| 
 | ||||
|     default: | ||||
|         qemu_log_mask(LOG_UNIMP, "%s: unknown register 0x%02" HWADDR_PRIx "\n", | ||||
|                       __func__, addr); | ||||
|         break; | ||||
|     } | ||||
|     trace_lasips2_reg_read(size, port->id, addr, | ||||
|                            artist_read_reg_name(addr), ret); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| static const MemoryRegionOps lasips2_reg_ops = { | ||||
|     .read = lasips2_reg_read, | ||||
|     .write = lasips2_reg_write, | ||||
|     .impl = { | ||||
|         .min_access_size = 1, | ||||
|         .max_access_size = 4, | ||||
|     }, | ||||
|     .endianness = DEVICE_NATIVE_ENDIAN, | ||||
| }; | ||||
| 
 | ||||
| static void ps2dev_update_irq(void *opaque, int level) | ||||
| { | ||||
|     LASIPS2Port *port = opaque; | ||||
|     port->irq = level; | ||||
|     lasips2_update_irq(port->parent); | ||||
| } | ||||
| 
 | ||||
| void lasips2_init(MemoryRegion *address_space, | ||||
|                   hwaddr base, qemu_irq irq) | ||||
| { | ||||
|     LASIPS2State *s; | ||||
| 
 | ||||
|     s = g_malloc0(sizeof(LASIPS2State)); | ||||
| 
 | ||||
|     s->irq = irq; | ||||
|     s->mouse.id = 1; | ||||
|     s->kbd.parent = s; | ||||
|     s->mouse.parent = s; | ||||
| 
 | ||||
|     vmstate_register(NULL, base, &vmstate_lasips2, s); | ||||
| 
 | ||||
|     s->kbd.dev = ps2_kbd_init(ps2dev_update_irq, &s->kbd); | ||||
|     s->mouse.dev = ps2_mouse_init(ps2dev_update_irq, &s->mouse); | ||||
| 
 | ||||
|     memory_region_init_io(&s->kbd.reg, NULL, &lasips2_reg_ops, &s->kbd, | ||||
|                           "lasips2-kbd", 0x100); | ||||
|     memory_region_add_subregion(address_space, base, &s->kbd.reg); | ||||
| 
 | ||||
|     memory_region_init_io(&s->mouse.reg, NULL, &lasips2_reg_ops, &s->mouse, | ||||
|                           "lasips2-mouse", 0x100); | ||||
|     memory_region_add_subregion(address_space, base + 0x100, &s->mouse.reg); | ||||
| } | ||||
| @ -49,6 +49,8 @@ | ||||
| #define KBD_CMD_RESET_DISABLE	0xF5	/* reset and disable scanning */ | ||||
| #define KBD_CMD_RESET_ENABLE   	0xF6    /* reset and enable scanning */ | ||||
| #define KBD_CMD_RESET		0xFF	/* Reset */ | ||||
| #define KBD_CMD_SET_MAKE_BREAK  0xFC    /* Set Make and Break mode */ | ||||
| #define KBD_CMD_SET_TYPEMATIC   0xFA    /* Set Typematic Make and Break mode */ | ||||
| 
 | ||||
| /* Keyboard Replies */ | ||||
| #define KBD_REPLY_POR		0xAA	/* Power on reset */ | ||||
| @ -190,6 +192,11 @@ static void ps2_reset_queue(PS2State *s) | ||||
|     q->count = 0; | ||||
| } | ||||
| 
 | ||||
| int ps2_queue_empty(PS2State *s) | ||||
| { | ||||
|     return s->queue.count == 0; | ||||
| } | ||||
| 
 | ||||
| void ps2_queue_noirq(PS2State *s, int b) | ||||
| { | ||||
|     PS2Queue *q = &s->queue; | ||||
| @ -573,6 +580,7 @@ void ps2_write_keyboard(void *opaque, int val) | ||||
|         case KBD_CMD_SCANCODE: | ||||
|         case KBD_CMD_SET_LEDS: | ||||
|         case KBD_CMD_SET_RATE: | ||||
|         case KBD_CMD_SET_MAKE_BREAK: | ||||
|             s->common.write_cmd = val; | ||||
|             ps2_queue(&s->common, KBD_REPLY_ACK); | ||||
|             break; | ||||
| @ -592,11 +600,18 @@ void ps2_write_keyboard(void *opaque, int val) | ||||
|                 KBD_REPLY_ACK, | ||||
|                 KBD_REPLY_POR); | ||||
|             break; | ||||
|         case KBD_CMD_SET_TYPEMATIC: | ||||
|             ps2_queue(&s->common, KBD_REPLY_ACK); | ||||
|             break; | ||||
|         default: | ||||
|             ps2_queue(&s->common, KBD_REPLY_RESEND); | ||||
|             break; | ||||
|         } | ||||
|         break; | ||||
|     case KBD_CMD_SET_MAKE_BREAK: | ||||
|         ps2_queue(&s->common, KBD_REPLY_ACK); | ||||
|         s->common.write_cmd = -1; | ||||
|         break; | ||||
|     case KBD_CMD_SCANCODE: | ||||
|         if (val == 0) { | ||||
|             if (s->common.queue.count <= PS2_QUEUE_SIZE - 2) { | ||||
|  | ||||
| @ -53,3 +53,8 @@ tsc2005_sense(const char *state) "touchscreen sense %s" | ||||
| 
 | ||||
| # virtio-input.c | ||||
| virtio_input_queue_full(void) "queue full" | ||||
| 
 | ||||
| # lasips2.c | ||||
| lasips2_reg_read(unsigned int size, int id, uint64_t addr, const char *name, uint64_t val) "%u %d addr 0x%"PRIx64 "%s -> 0x%"PRIx64 | ||||
| lasips2_reg_write(unsigned int size, int id, uint64_t addr, const char *name, uint64_t val) "%u %d addr 0x%"PRIx64 "%s <- 0x%"PRIx64 | ||||
| lasips2_intr(unsigned int val) "%d" | ||||
|  | ||||
| @ -31,6 +31,9 @@ config TULIP | ||||
|     depends on PCI | ||||
|     select NMC93XX_EEPROM | ||||
| 
 | ||||
| config I82596_COMMON | ||||
|     bool | ||||
| 
 | ||||
| config E1000_PCI | ||||
|     bool | ||||
|     default y if PCI_DEVICES | ||||
| @ -89,6 +92,10 @@ config LANCE | ||||
|     bool | ||||
|     select PCNET_COMMON | ||||
| 
 | ||||
| config LASI_82596 | ||||
|     bool | ||||
|     select I82596_COMMON | ||||
| 
 | ||||
| config SUNHME | ||||
|     bool | ||||
| 
 | ||||
|  | ||||
| @ -28,6 +28,8 @@ common-obj-$(CONFIG_IMX_FEC) += imx_fec.o | ||||
| common-obj-$(CONFIG_CADENCE) += cadence_gem.o | ||||
| common-obj-$(CONFIG_STELLARIS_ENET) += stellaris_enet.o | ||||
| common-obj-$(CONFIG_LANCE) += lance.o | ||||
| common-obj-$(CONFIG_LASI_82596) += lasi_i82596.o | ||||
| common-obj-$(CONFIG_I82596_COMMON) += i82596.o | ||||
| common-obj-$(CONFIG_SUNHME) += sunhme.o | ||||
| common-obj-$(CONFIG_FTGMAC100) += ftgmac100.o | ||||
| common-obj-$(CONFIG_SUNGEM) += sungem.o | ||||
|  | ||||
							
								
								
									
										734
									
								
								hw/net/i82596.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										734
									
								
								hw/net/i82596.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,734 @@ | ||||
| /*
 | ||||
|  * QEMU Intel i82596 (Apricot) emulation | ||||
|  * | ||||
|  * Copyright (c) 2019 Helge Deller <deller@gmx.de> | ||||
|  * This work is licensed under the GNU GPL license version 2 or later. | ||||
|  * | ||||
|  * This software was written to be compatible with the specification: | ||||
|  * https://www.intel.com/assets/pdf/general/82596ca.pdf
 | ||||
|  */ | ||||
| 
 | ||||
| #include "qemu/osdep.h" | ||||
| #include "qemu/timer.h" | ||||
| #include "net/net.h" | ||||
| #include "net/eth.h" | ||||
| #include "sysemu/sysemu.h" | ||||
| #include "hw/irq.h" | ||||
| #include "hw/qdev-properties.h" | ||||
| #include "migration/vmstate.h" | ||||
| #include "qemu/module.h" | ||||
| #include "trace.h" | ||||
| #include "i82596.h" | ||||
| #include <zlib.h>       /* For crc32 */ | ||||
| 
 | ||||
| #if defined(ENABLE_DEBUG) | ||||
| #define DBG(x)          x | ||||
| #else | ||||
| #define DBG(x)          do { } while (0) | ||||
| #endif | ||||
| 
 | ||||
| #define USE_TIMER       0 | ||||
| 
 | ||||
| #define BITS(n, m) (((0xffffffffU << (31 - n)) >> (31 - n + m)) << m) | ||||
| 
 | ||||
| #define PKT_BUF_SZ      1536 | ||||
| #define MAX_MC_CNT      64 | ||||
| 
 | ||||
| #define ISCP_BUSY       0x0001 | ||||
| 
 | ||||
| #define I596_NULL       ((uint32_t)0xffffffff) | ||||
| 
 | ||||
| #define SCB_STATUS_CX   0x8000 /* CU finished command with I bit */ | ||||
| #define SCB_STATUS_FR   0x4000 /* RU finished receiving a frame */ | ||||
| #define SCB_STATUS_CNA  0x2000 /* CU left active state */ | ||||
| #define SCB_STATUS_RNR  0x1000 /* RU left active state */ | ||||
| 
 | ||||
| #define CU_IDLE         0 | ||||
| #define CU_SUSPENDED    1 | ||||
| #define CU_ACTIVE       2 | ||||
| 
 | ||||
| #define RX_IDLE         0 | ||||
| #define RX_SUSPENDED    1 | ||||
| #define RX_READY        4 | ||||
| 
 | ||||
| #define CMD_EOL         0x8000  /* The last command of the list, stop. */ | ||||
| #define CMD_SUSP        0x4000  /* Suspend after doing cmd. */ | ||||
| #define CMD_INTR        0x2000  /* Interrupt after doing cmd. */ | ||||
| 
 | ||||
| #define CMD_FLEX        0x0008  /* Enable flexible memory model */ | ||||
| 
 | ||||
| enum commands { | ||||
|         CmdNOp = 0, CmdSASetup = 1, CmdConfigure = 2, CmdMulticastList = 3, | ||||
|         CmdTx = 4, CmdTDR = 5, CmdDump = 6, CmdDiagnose = 7 | ||||
| }; | ||||
| 
 | ||||
| #define STAT_C          0x8000  /* Set to 0 after execution */ | ||||
| #define STAT_B          0x4000  /* Command being executed */ | ||||
| #define STAT_OK         0x2000  /* Command executed ok */ | ||||
| #define STAT_A          0x1000  /* Command aborted */ | ||||
| 
 | ||||
| #define I596_EOF        0x8000 | ||||
| #define SIZE_MASK       0x3fff | ||||
| 
 | ||||
| #define ETHER_TYPE_LEN 2 | ||||
| #define VLAN_TCI_LEN 2 | ||||
| #define VLAN_HLEN (ETHER_TYPE_LEN + VLAN_TCI_LEN) | ||||
| 
 | ||||
| /* various flags in the chip config registers */ | ||||
| #define I596_PREFETCH   (s->config[0] & 0x80) | ||||
| #define I596_PROMISC    (s->config[8] & 0x01) | ||||
| #define I596_BC_DISABLE (s->config[8] & 0x02) /* broadcast disable */ | ||||
| #define I596_NOCRC_INS  (s->config[8] & 0x08) | ||||
| #define I596_CRCINM     (s->config[11] & 0x04) /* CRC appended */ | ||||
| #define I596_MC_ALL     (s->config[11] & 0x20) | ||||
| #define I596_MULTIIA    (s->config[13] & 0x40) | ||||
| 
 | ||||
| 
 | ||||
| static uint8_t get_byte(uint32_t addr) | ||||
| { | ||||
|     return ldub_phys(&address_space_memory, addr); | ||||
| } | ||||
| 
 | ||||
| static void set_byte(uint32_t addr, uint8_t c) | ||||
| { | ||||
|     return stb_phys(&address_space_memory, addr, c); | ||||
| } | ||||
| 
 | ||||
| static uint16_t get_uint16(uint32_t addr) | ||||
| { | ||||
|     return lduw_be_phys(&address_space_memory, addr); | ||||
| } | ||||
| 
 | ||||
| static void set_uint16(uint32_t addr, uint16_t w) | ||||
| { | ||||
|     return stw_be_phys(&address_space_memory, addr, w); | ||||
| } | ||||
| 
 | ||||
| static uint32_t get_uint32(uint32_t addr) | ||||
| { | ||||
|     uint32_t lo = lduw_be_phys(&address_space_memory, addr); | ||||
|     uint32_t hi = lduw_be_phys(&address_space_memory, addr + 2); | ||||
|     return (hi << 16) | lo; | ||||
| } | ||||
| 
 | ||||
| static void set_uint32(uint32_t addr, uint32_t val) | ||||
| { | ||||
|     set_uint16(addr, (uint16_t) val); | ||||
|     set_uint16(addr + 2, val >> 16); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| struct qemu_ether_header { | ||||
|     uint8_t ether_dhost[6]; | ||||
|     uint8_t ether_shost[6]; | ||||
|     uint16_t ether_type; | ||||
| }; | ||||
| 
 | ||||
| #define PRINT_PKTHDR(txt, BUF) do {                  \ | ||||
|     struct qemu_ether_header *hdr = (void *)(BUF); \ | ||||
|     printf(txt ": packet dhost=" MAC_FMT ", shost=" MAC_FMT ", type=0x%04x\n",\ | ||||
|            MAC_ARG(hdr->ether_dhost), MAC_ARG(hdr->ether_shost),        \ | ||||
|            be16_to_cpu(hdr->ether_type));       \ | ||||
| } while (0) | ||||
| 
 | ||||
| static void i82596_transmit(I82596State *s, uint32_t addr) | ||||
| { | ||||
|     uint32_t tdb_p; /* Transmit Buffer Descriptor */ | ||||
| 
 | ||||
|     /* TODO: Check flexible mode */ | ||||
|     tdb_p = get_uint32(addr + 8); | ||||
|     while (tdb_p != I596_NULL) { | ||||
|         uint16_t size, len; | ||||
|         uint32_t tba; | ||||
| 
 | ||||
|         size = get_uint16(tdb_p); | ||||
|         len = size & SIZE_MASK; | ||||
|         tba = get_uint32(tdb_p + 8); | ||||
|         trace_i82596_transmit(len, tba); | ||||
| 
 | ||||
|         if (s->nic && len) { | ||||
|             assert(len <= sizeof(s->tx_buffer)); | ||||
|             address_space_rw(&address_space_memory, tba, | ||||
|                 MEMTXATTRS_UNSPECIFIED, s->tx_buffer, len, 0); | ||||
|             DBG(PRINT_PKTHDR("Send", &s->tx_buffer)); | ||||
|             DBG(printf("Sending %d bytes\n", len)); | ||||
|             qemu_send_packet(qemu_get_queue(s->nic), s->tx_buffer, len); | ||||
|         } | ||||
| 
 | ||||
|         /* was this the last package? */ | ||||
|         if (size & I596_EOF) { | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         /* get next buffer pointer */ | ||||
|         tdb_p = get_uint32(tdb_p + 4); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void set_individual_address(I82596State *s, uint32_t addr) | ||||
| { | ||||
|     NetClientState *nc; | ||||
|     uint8_t *m; | ||||
| 
 | ||||
|     nc = qemu_get_queue(s->nic); | ||||
|     m = s->conf.macaddr.a; | ||||
|     address_space_rw(&address_space_memory, addr + 8, | ||||
|         MEMTXATTRS_UNSPECIFIED, m, ETH_ALEN, 0); | ||||
|     qemu_format_nic_info_str(nc, m); | ||||
|     trace_i82596_new_mac(nc->info_str); | ||||
| } | ||||
| 
 | ||||
| static void set_multicast_list(I82596State *s, uint32_t addr) | ||||
| { | ||||
|     uint16_t mc_count, i; | ||||
| 
 | ||||
|     memset(&s->mult[0], 0, sizeof(s->mult)); | ||||
|     mc_count = get_uint16(addr + 8) / ETH_ALEN; | ||||
|     addr += 10; | ||||
|     if (mc_count > MAX_MC_CNT) { | ||||
|         mc_count = MAX_MC_CNT; | ||||
|     } | ||||
|     for (i = 0; i < mc_count; i++) { | ||||
|         uint8_t multicast_addr[ETH_ALEN]; | ||||
|         address_space_rw(&address_space_memory, | ||||
|             addr + i * ETH_ALEN, MEMTXATTRS_UNSPECIFIED, | ||||
|             multicast_addr, ETH_ALEN, 0); | ||||
|         DBG(printf("Add multicast entry " MAC_FMT "\n", | ||||
|                     MAC_ARG(multicast_addr))); | ||||
|         unsigned mcast_idx = (net_crc32(multicast_addr, ETH_ALEN) & | ||||
|                               BITS(7, 2)) >> 2; | ||||
|         assert(mcast_idx < 8 * sizeof(s->mult)); | ||||
|         s->mult[mcast_idx >> 3] |= (1 << (mcast_idx & 7)); | ||||
|     } | ||||
|     trace_i82596_set_multicast(mc_count); | ||||
| } | ||||
| 
 | ||||
| void i82596_set_link_status(NetClientState *nc) | ||||
| { | ||||
|     I82596State *d = qemu_get_nic_opaque(nc); | ||||
| 
 | ||||
|     d->lnkst = nc->link_down ? 0 : 0x8000; | ||||
| } | ||||
| 
 | ||||
| static void update_scb_status(I82596State *s) | ||||
| { | ||||
|     s->scb_status = (s->scb_status & 0xf000) | ||||
|         | (s->cu_status << 8) | (s->rx_status << 4); | ||||
|     set_uint16(s->scb, s->scb_status); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void i82596_s_reset(I82596State *s) | ||||
| { | ||||
|     trace_i82596_s_reset(s); | ||||
|     s->scp = 0; | ||||
|     s->scb_status = 0; | ||||
|     s->cu_status = CU_IDLE; | ||||
|     s->rx_status = RX_SUSPENDED; | ||||
|     s->cmd_p = I596_NULL; | ||||
|     s->lnkst = 0x8000; /* initial link state: up */ | ||||
|     s->ca = s->ca_active = 0; | ||||
|     s->send_irq = 0; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void command_loop(I82596State *s) | ||||
| { | ||||
|     uint16_t cmd; | ||||
|     uint16_t status; | ||||
|     uint8_t byte_cnt; | ||||
| 
 | ||||
|     DBG(printf("STARTING COMMAND LOOP cmd_p=%08x\n", s->cmd_p)); | ||||
| 
 | ||||
|     while (s->cmd_p != I596_NULL) { | ||||
|         /* set status */ | ||||
|         status = STAT_B; | ||||
|         set_uint16(s->cmd_p, status); | ||||
|         status = STAT_C | STAT_OK; /* update, but write later */ | ||||
| 
 | ||||
|         cmd = get_uint16(s->cmd_p + 2); | ||||
|         DBG(printf("Running command %04x at %08x\n", cmd, s->cmd_p)); | ||||
| 
 | ||||
|         switch (cmd & 0x07) { | ||||
|         case CmdNOp: | ||||
|             break; | ||||
|         case CmdSASetup: | ||||
|             set_individual_address(s, s->cmd_p); | ||||
|             break; | ||||
|         case CmdConfigure: | ||||
|             byte_cnt = get_byte(s->cmd_p + 8) & 0x0f; | ||||
|             byte_cnt = MAX(byte_cnt, 4); | ||||
|             byte_cnt = MIN(byte_cnt, sizeof(s->config)); | ||||
|             /* copy byte_cnt max. */ | ||||
|             address_space_rw(&address_space_memory, s->cmd_p + 8, | ||||
|                 MEMTXATTRS_UNSPECIFIED, s->config, byte_cnt, 0); | ||||
|             /* config byte according to page 35ff */ | ||||
|             s->config[2] &= 0x82; /* mask valid bits */ | ||||
|             s->config[2] |= 0x40; | ||||
|             s->config[7]  &= 0xf7; /* clear zero bit */ | ||||
|             assert(I596_NOCRC_INS == 0); /* do CRC insertion */ | ||||
|             s->config[10] = MAX(s->config[10], 5); /* min frame length */ | ||||
|             s->config[12] &= 0x40; /* only full duplex field valid */ | ||||
|             s->config[13] |= 0x3f; /* set ones in byte 13 */ | ||||
|             break; | ||||
|         case CmdTDR: | ||||
|             /* get signal LINK */ | ||||
|             set_uint32(s->cmd_p + 8, s->lnkst); | ||||
|             break; | ||||
|         case CmdTx: | ||||
|             i82596_transmit(s, s->cmd_p); | ||||
|             break; | ||||
|         case CmdMulticastList: | ||||
|             set_multicast_list(s, s->cmd_p); | ||||
|             break; | ||||
|         case CmdDump: | ||||
|         case CmdDiagnose: | ||||
|             printf("FIXME Command %d !!\n", cmd & 7); | ||||
|             assert(0); | ||||
|         } | ||||
| 
 | ||||
|         /* update status */ | ||||
|         set_uint16(s->cmd_p, status); | ||||
| 
 | ||||
|         s->cmd_p = get_uint32(s->cmd_p + 4); /* get link address */ | ||||
|         DBG(printf("NEXT addr would be %08x\n", s->cmd_p)); | ||||
|         if (s->cmd_p == 0) { | ||||
|             s->cmd_p = I596_NULL; | ||||
|         } | ||||
| 
 | ||||
|         /* Stop when last command of the list. */ | ||||
|         if (cmd & CMD_EOL) { | ||||
|             s->cmd_p = I596_NULL; | ||||
|         } | ||||
|         /* Suspend after doing cmd? */ | ||||
|         if (cmd & CMD_SUSP) { | ||||
|             s->cu_status = CU_SUSPENDED; | ||||
|             printf("FIXME SUSPEND !!\n"); | ||||
|         } | ||||
|         /* Interrupt after doing cmd? */ | ||||
|         if (cmd & CMD_INTR) { | ||||
|             s->scb_status |= SCB_STATUS_CX; | ||||
|         } else { | ||||
|             s->scb_status &= ~SCB_STATUS_CX; | ||||
|         } | ||||
|         update_scb_status(s); | ||||
| 
 | ||||
|         /* Interrupt after doing cmd? */ | ||||
|         if (cmd & CMD_INTR) { | ||||
|             s->send_irq = 1; | ||||
|         } | ||||
| 
 | ||||
|         if (s->cu_status != CU_ACTIVE) { | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     DBG(printf("FINISHED COMMAND LOOP\n")); | ||||
|     qemu_flush_queued_packets(qemu_get_queue(s->nic)); | ||||
| } | ||||
| 
 | ||||
| static void i82596_flush_queue_timer(void *opaque) | ||||
| { | ||||
|     I82596State *s = opaque; | ||||
|     if (0) { | ||||
|         timer_del(s->flush_queue_timer); | ||||
|         qemu_flush_queued_packets(qemu_get_queue(s->nic)); | ||||
|         timer_mod(s->flush_queue_timer, | ||||
|               qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 1000); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void examine_scb(I82596State *s) | ||||
| { | ||||
|     uint16_t command, cuc, ruc; | ||||
| 
 | ||||
|     /* get the scb command word */ | ||||
|     command = get_uint16(s->scb + 2); | ||||
|     cuc = (command >> 8) & 0x7; | ||||
|     ruc = (command >> 4) & 0x7; | ||||
|     DBG(printf("MAIN COMMAND %04x  cuc %02x ruc %02x\n", command, cuc, ruc)); | ||||
|     /* and clear the scb command word */ | ||||
|     set_uint16(s->scb + 2, 0); | ||||
| 
 | ||||
|     if (command & BIT(31))      /* ACK-CX */ | ||||
|         s->scb_status &= ~SCB_STATUS_CX; | ||||
|     if (command & BIT(30))      /*ACK-FR */ | ||||
|         s->scb_status &= ~SCB_STATUS_FR; | ||||
|     if (command & BIT(29))      /*ACK-CNA */ | ||||
|         s->scb_status &= ~SCB_STATUS_CNA; | ||||
|     if (command & BIT(28))      /*ACK-RNR */ | ||||
|         s->scb_status &= ~SCB_STATUS_RNR; | ||||
| 
 | ||||
|     switch (cuc) { | ||||
|     case 0:     /* no change */ | ||||
|         break; | ||||
|     case 1:     /* CUC_START */ | ||||
|         s->cu_status = CU_ACTIVE; | ||||
|         break; | ||||
|     case 4:     /* CUC_ABORT */ | ||||
|         s->cu_status = CU_SUSPENDED; | ||||
|         s->scb_status |= SCB_STATUS_CNA; /* CU left active state */ | ||||
|         break; | ||||
|     default: | ||||
|         printf("WARNING: Unknown CUC %d!\n", cuc); | ||||
|     } | ||||
| 
 | ||||
|     switch (ruc) { | ||||
|     case 0:     /* no change */ | ||||
|         break; | ||||
|     case 1:     /* RX_START */ | ||||
|     case 2:     /* RX_RESUME */ | ||||
|         s->rx_status = RX_IDLE; | ||||
|         if (USE_TIMER) { | ||||
|             timer_mod(s->flush_queue_timer, qemu_clock_get_ms( | ||||
|                                 QEMU_CLOCK_VIRTUAL) + 1000); | ||||
|         } | ||||
|         break; | ||||
|     case 3:     /* RX_SUSPEND */ | ||||
|     case 4:     /* RX_ABORT */ | ||||
|         s->rx_status = RX_SUSPENDED; | ||||
|         s->scb_status |= SCB_STATUS_RNR; /* RU left active state */ | ||||
|         break; | ||||
|     default: | ||||
|         printf("WARNING: Unknown RUC %d!\n", ruc); | ||||
|     } | ||||
| 
 | ||||
|     if (command & 0x80) { /* reset bit set? */ | ||||
|         i82596_s_reset(s); | ||||
|     } | ||||
| 
 | ||||
|     /* execute commands from SCBL */ | ||||
|     if (s->cu_status != CU_SUSPENDED) { | ||||
|         if (s->cmd_p == I596_NULL) { | ||||
|             s->cmd_p = get_uint32(s->scb + 4); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /* update scb status */ | ||||
|     update_scb_status(s); | ||||
| 
 | ||||
|     command_loop(s); | ||||
| } | ||||
| 
 | ||||
| static void signal_ca(I82596State *s) | ||||
| { | ||||
|     uint32_t iscp = 0; | ||||
| 
 | ||||
|     /* trace_i82596_channel_attention(s); */ | ||||
|     if (s->scp) { | ||||
|         /* CA after reset -> do init with new scp. */ | ||||
|         s->sysbus = get_byte(s->scp + 3); /* big endian */ | ||||
|         DBG(printf("SYSBUS = %08x\n", s->sysbus)); | ||||
|         if (((s->sysbus >> 1) & 0x03) != 2) { | ||||
|             printf("WARNING: NO LINEAR MODE !!\n"); | ||||
|         } | ||||
|         if ((s->sysbus >> 7)) { | ||||
|             printf("WARNING: 32BIT LINMODE IN B-STEPPING NOT SUPPORTED !!\n"); | ||||
|         } | ||||
|         iscp = get_uint32(s->scp + 8); | ||||
|         s->scb = get_uint32(iscp + 4); | ||||
|         set_byte(iscp + 1, 0); /* clear BUSY flag in iscp */ | ||||
|         s->scp = 0; | ||||
|     } | ||||
| 
 | ||||
|     s->ca++;    /* count ca() */ | ||||
|     if (!s->ca_active) { | ||||
|         s->ca_active = 1; | ||||
|         while (s->ca)   { | ||||
|             examine_scb(s); | ||||
|             s->ca--; | ||||
|         } | ||||
|         s->ca_active = 0; | ||||
|     } | ||||
| 
 | ||||
|     if (s->send_irq) { | ||||
|         s->send_irq = 0; | ||||
|         qemu_set_irq(s->irq, 1); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void i82596_ioport_writew(void *opaque, uint32_t addr, uint32_t val) | ||||
| { | ||||
|     I82596State *s = opaque; | ||||
|     /* printf("i82596_ioport_writew addr=0x%08x val=0x%04x\n", addr, val); */ | ||||
|     switch (addr) { | ||||
|     case PORT_RESET: /* Reset */ | ||||
|         i82596_s_reset(s); | ||||
|         break; | ||||
|     case PORT_ALTSCP: | ||||
|         s->scp = val; | ||||
|         break; | ||||
|     case PORT_CA: | ||||
|         signal_ca(s); | ||||
|         break; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| uint32_t i82596_ioport_readw(void *opaque, uint32_t addr) | ||||
| { | ||||
|     return -1; | ||||
| } | ||||
| 
 | ||||
| void i82596_h_reset(void *opaque) | ||||
| { | ||||
|     I82596State *s = opaque; | ||||
| 
 | ||||
|     i82596_s_reset(s); | ||||
| } | ||||
| 
 | ||||
| int i82596_can_receive(NetClientState *nc) | ||||
| { | ||||
|     I82596State *s = qemu_get_nic_opaque(nc); | ||||
| 
 | ||||
|     if (s->rx_status == RX_SUSPENDED) { | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     if (!s->lnkst) { | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     if (USE_TIMER && !timer_pending(s->flush_queue_timer)) { | ||||
|         return 1; | ||||
|     } | ||||
| 
 | ||||
|     return 1; | ||||
| } | ||||
| 
 | ||||
| #define MIN_BUF_SIZE 60 | ||||
| 
 | ||||
| ssize_t i82596_receive(NetClientState *nc, const uint8_t *buf, size_t sz) | ||||
| { | ||||
|     I82596State *s = qemu_get_nic_opaque(nc); | ||||
|     uint32_t rfd_p; | ||||
|     uint32_t rbd; | ||||
|     uint16_t is_broadcast = 0; | ||||
|     size_t len = sz; | ||||
|     uint32_t crc; | ||||
|     uint8_t *crc_ptr; | ||||
|     uint8_t buf1[MIN_BUF_SIZE + VLAN_HLEN]; | ||||
|     static const uint8_t broadcast_macaddr[6] = { | ||||
|                 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; | ||||
| 
 | ||||
|     DBG(printf("i82596_receive() start\n")); | ||||
| 
 | ||||
|     if (USE_TIMER && timer_pending(s->flush_queue_timer)) { | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     /* first check if receiver is enabled */ | ||||
|     if (s->rx_status == RX_SUSPENDED) { | ||||
|         trace_i82596_receive_analysis(">>> Receiving suspended"); | ||||
|         return -1; | ||||
|     } | ||||
| 
 | ||||
|     if (!s->lnkst) { | ||||
|         trace_i82596_receive_analysis(">>> Link down"); | ||||
|         return -1; | ||||
|     } | ||||
| 
 | ||||
|     /* Received frame smaller than configured "min frame len"? */ | ||||
|     if (sz < s->config[10]) { | ||||
|         printf("Received frame too small, %zu vs. %u bytes\n", | ||||
|                sz, s->config[10]); | ||||
|         return -1; | ||||
|     } | ||||
| 
 | ||||
|     DBG(printf("Received %lu bytes\n", sz)); | ||||
| 
 | ||||
|     if (I596_PROMISC) { | ||||
| 
 | ||||
|         /* promiscuous: receive all */ | ||||
|         trace_i82596_receive_analysis( | ||||
|                 ">>> packet received in promiscuous mode"); | ||||
| 
 | ||||
|     } else { | ||||
| 
 | ||||
|         if (!memcmp(buf,  broadcast_macaddr, 6)) { | ||||
|             /* broadcast address */ | ||||
|             if (I596_BC_DISABLE) { | ||||
|                 trace_i82596_receive_analysis(">>> broadcast packet rejected"); | ||||
| 
 | ||||
|                 return len; | ||||
|             } | ||||
| 
 | ||||
|             trace_i82596_receive_analysis(">>> broadcast packet received"); | ||||
|             is_broadcast = 1; | ||||
| 
 | ||||
|         } else if (buf[0] & 0x01) { | ||||
|             /* multicast */ | ||||
|             if (!I596_MC_ALL) { | ||||
|                 trace_i82596_receive_analysis(">>> multicast packet rejected"); | ||||
| 
 | ||||
|                 return len; | ||||
|             } | ||||
| 
 | ||||
|             int mcast_idx = (net_crc32(buf, ETH_ALEN) & BITS(7, 2)) >> 2; | ||||
|             assert(mcast_idx < 8 * sizeof(s->mult)); | ||||
| 
 | ||||
|             if (!(s->mult[mcast_idx >> 3] & (1 << (mcast_idx & 7)))) { | ||||
|                 trace_i82596_receive_analysis(">>> multicast address mismatch"); | ||||
| 
 | ||||
|                 return len; | ||||
|             } | ||||
| 
 | ||||
|             trace_i82596_receive_analysis(">>> multicast packet received"); | ||||
|             is_broadcast = 1; | ||||
| 
 | ||||
|         } else if (!memcmp(s->conf.macaddr.a, buf, 6)) { | ||||
| 
 | ||||
|             /* match */ | ||||
|             trace_i82596_receive_analysis( | ||||
|                     ">>> physical address matching packet received"); | ||||
| 
 | ||||
|         } else { | ||||
| 
 | ||||
|             trace_i82596_receive_analysis(">>> unknown packet"); | ||||
| 
 | ||||
|             return len; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /* if too small buffer, then expand it */ | ||||
|     if (len < MIN_BUF_SIZE + VLAN_HLEN) { | ||||
|         memcpy(buf1, buf, len); | ||||
|         memset(buf1 + len, 0, MIN_BUF_SIZE + VLAN_HLEN - len); | ||||
|         buf = buf1; | ||||
|         if (len < MIN_BUF_SIZE) { | ||||
|             len = MIN_BUF_SIZE; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /* Calculate the ethernet checksum (4 bytes) */ | ||||
|     len += 4; | ||||
|     crc = cpu_to_be32(crc32(~0, buf, sz)); | ||||
|     crc_ptr = (uint8_t *) &crc; | ||||
| 
 | ||||
|     rfd_p = get_uint32(s->scb + 8); /* get Receive Frame Descriptor */ | ||||
|     assert(rfd_p && rfd_p != I596_NULL); | ||||
| 
 | ||||
|     /* get first Receive Buffer Descriptor Address */ | ||||
|     rbd = get_uint32(rfd_p + 8); | ||||
|     assert(rbd && rbd != I596_NULL); | ||||
| 
 | ||||
|     trace_i82596_receive_packet(len); | ||||
|     /* PRINT_PKTHDR("Receive", buf); */ | ||||
| 
 | ||||
|     while (len) { | ||||
|         uint16_t command, status; | ||||
|         uint32_t next_rfd; | ||||
| 
 | ||||
|         command = get_uint16(rfd_p + 2); | ||||
|         assert(command & CMD_FLEX); /* assert Flex Mode */ | ||||
|         /* get first Receive Buffer Descriptor Address */ | ||||
|         rbd = get_uint32(rfd_p + 8); | ||||
|         assert(get_uint16(rfd_p + 14) == 0); | ||||
| 
 | ||||
|         /* printf("Receive: rfd is %08x\n", rfd_p); */ | ||||
| 
 | ||||
|         while (len) { | ||||
|             uint16_t buffer_size, num; | ||||
|             uint32_t rba; | ||||
| 
 | ||||
|             /* printf("Receive: rbd is %08x\n", rbd); */ | ||||
|             buffer_size = get_uint16(rbd + 12); | ||||
|             /* printf("buffer_size is 0x%x\n", buffer_size); */ | ||||
|             assert(buffer_size != 0); | ||||
| 
 | ||||
|             num = buffer_size & SIZE_MASK; | ||||
|             if (num > len) { | ||||
|                 num = len; | ||||
|             } | ||||
|             rba = get_uint32(rbd + 8); | ||||
|             /* printf("rba is 0x%x\n", rba); */ | ||||
|             address_space_rw(&address_space_memory, rba, | ||||
|                 MEMTXATTRS_UNSPECIFIED, (void *)buf, num, 1); | ||||
|             rba += num; | ||||
|             buf += num; | ||||
|             len -= num; | ||||
|             if (len == 0) { /* copy crc */ | ||||
|                 address_space_rw(&address_space_memory, rba - 4, | ||||
|                     MEMTXATTRS_UNSPECIFIED, crc_ptr, 4, 1); | ||||
|             } | ||||
| 
 | ||||
|             num |= 0x4000; /* set F BIT */ | ||||
|             if (len == 0) { | ||||
|                 num |= I596_EOF; /* set EOF BIT */ | ||||
|             } | ||||
|             set_uint16(rbd + 0, num); /* write actual count with flags */ | ||||
| 
 | ||||
|             /* get next rbd */ | ||||
|             rbd = get_uint32(rbd + 4); | ||||
|             /* printf("Next Receive: rbd is %08x\n", rbd); */ | ||||
| 
 | ||||
|             if (buffer_size & I596_EOF) /* last entry */ | ||||
|                 break; | ||||
|         } | ||||
| 
 | ||||
|         /* Housekeeping, see pg. 18 */ | ||||
|         next_rfd = get_uint32(rfd_p + 4); | ||||
|         set_uint32(next_rfd + 8, rbd); | ||||
| 
 | ||||
|         status = STAT_C | STAT_OK | is_broadcast; | ||||
|         set_uint16(rfd_p, status); | ||||
| 
 | ||||
|         if (command & CMD_SUSP) {  /* suspend after command? */ | ||||
|             s->rx_status = RX_SUSPENDED; | ||||
|             s->scb_status |= SCB_STATUS_RNR; /* RU left active state */ | ||||
|             break; | ||||
|         } | ||||
|         if (command & CMD_EOL) /* was it last Frame Descriptor? */ | ||||
|             break; | ||||
| 
 | ||||
|         assert(len == 0); | ||||
|     } | ||||
| 
 | ||||
|     assert(len == 0); | ||||
| 
 | ||||
|     s->scb_status |= SCB_STATUS_FR; /* set "RU finished receiving frame" bit. */ | ||||
|     update_scb_status(s); | ||||
| 
 | ||||
|     /* send IRQ that we received data */ | ||||
|     qemu_set_irq(s->irq, 1); | ||||
|     /* s->send_irq = 1; */ | ||||
| 
 | ||||
|     if (0) { | ||||
|         DBG(printf("Checking:\n")); | ||||
|         rfd_p = get_uint32(s->scb + 8); /* get Receive Frame Descriptor */ | ||||
|         DBG(printf("Next Receive: rfd is %08x\n", rfd_p)); | ||||
|         rfd_p = get_uint32(rfd_p + 4); /* get Next Receive Frame Descriptor */ | ||||
|         DBG(printf("Next Receive: rfd is %08x\n", rfd_p)); | ||||
|         /* get first Receive Buffer Descriptor Address */ | ||||
|         rbd = get_uint32(rfd_p + 8); | ||||
|         DBG(printf("Next Receive: rbd is %08x\n", rbd)); | ||||
|     } | ||||
| 
 | ||||
|     return sz; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| const VMStateDescription vmstate_i82596 = { | ||||
|     .name = "i82596", | ||||
|     .version_id = 1, | ||||
|     .minimum_version_id = 1, | ||||
|     .fields = (VMStateField[]) { | ||||
|         VMSTATE_UINT16(lnkst, I82596State), | ||||
|         VMSTATE_TIMER_PTR(flush_queue_timer, I82596State), | ||||
|         VMSTATE_END_OF_LIST() | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| void i82596_common_init(DeviceState *dev, I82596State *s, NetClientInfo *info) | ||||
| { | ||||
|     if (s->conf.macaddr.a[0] == 0) { | ||||
|         qemu_macaddr_default_if_unset(&s->conf.macaddr); | ||||
|     } | ||||
|     s->nic = qemu_new_nic(info, &s->conf, object_get_typename(OBJECT(dev)), | ||||
|                 dev->id, s); | ||||
|     qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a); | ||||
| 
 | ||||
|     if (USE_TIMER) { | ||||
|         s->flush_queue_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, | ||||
|                                     i82596_flush_queue_timer, s); | ||||
|     } | ||||
|     s->lnkst = 0x8000; /* initial link state: up */ | ||||
| } | ||||
							
								
								
									
										55
									
								
								hw/net/i82596.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								hw/net/i82596.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | ||||
| #ifndef HW_I82596_H | ||||
| #define HW_I82596_H | ||||
| 
 | ||||
| #define I82596_IOPORT_SIZE       0x20 | ||||
| 
 | ||||
| #include "exec/memory.h" | ||||
| #include "exec/address-spaces.h" | ||||
| 
 | ||||
| #define PORT_RESET              0x00    /* reset 82596 */ | ||||
| #define PORT_SELFTEST           0x01    /* selftest */ | ||||
| #define PORT_ALTSCP             0x02    /* alternate SCB address */ | ||||
| #define PORT_ALTDUMP            0x03    /* Alternate DUMP address */ | ||||
| #define PORT_CA                 0x10    /* QEMU-internal CA signal */ | ||||
| 
 | ||||
| typedef struct I82596State_st I82596State; | ||||
| 
 | ||||
| struct I82596State_st { | ||||
|     MemoryRegion mmio; | ||||
|     MemoryRegion *as; | ||||
|     qemu_irq irq; | ||||
|     NICState *nic; | ||||
|     NICConf conf; | ||||
|     QEMUTimer *flush_queue_timer; | ||||
| 
 | ||||
|     hwaddr scp;         /* pointer to SCP */ | ||||
|     uint8_t sysbus; | ||||
|     uint32_t scb;       /* SCB */ | ||||
|     uint16_t scb_status; | ||||
|     uint8_t cu_status, rx_status; | ||||
|     uint16_t lnkst; | ||||
| 
 | ||||
|     uint32_t cmd_p;     /* addr of current command */ | ||||
|     int ca; | ||||
|     int ca_active; | ||||
|     int send_irq; | ||||
| 
 | ||||
|     /* Hash register (multicast mask array, multiple individual addresses). */ | ||||
|     uint8_t mult[8]; | ||||
|     uint8_t config[14]; /* config bytes from CONFIGURE command */ | ||||
| 
 | ||||
|     uint8_t tx_buffer[0x4000]; | ||||
| }; | ||||
| 
 | ||||
| void i82596_h_reset(void *opaque); | ||||
| void i82596_ioport_writew(void *opaque, uint32_t addr, uint32_t val); | ||||
| uint32_t i82596_ioport_readw(void *opaque, uint32_t addr); | ||||
| void i82596_ioport_writel(void *opaque, uint32_t addr, uint32_t val); | ||||
| uint32_t i82596_ioport_readl(void *opaque, uint32_t addr); | ||||
| uint32_t i82596_bcr_readw(I82596State *s, uint32_t rap); | ||||
| ssize_t i82596_receive(NetClientState *nc, const uint8_t *buf, size_t size_); | ||||
| int i82596_can_receive(NetClientState *nc); | ||||
| void i82596_set_link_status(NetClientState *nc); | ||||
| void i82596_common_init(DeviceState *dev, I82596State *s, NetClientInfo *info); | ||||
| extern const VMStateDescription vmstate_i82596; | ||||
| #endif | ||||
							
								
								
									
										188
									
								
								hw/net/lasi_i82596.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								hw/net/lasi_i82596.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,188 @@ | ||||
| /*
 | ||||
|  * QEMU LASI NIC i82596 emulation | ||||
|  * | ||||
|  * Copyright (c) 2019 Helge Deller <deller@gmx.de> | ||||
|  * This work is licensed under the GNU GPL license version 2 or later. | ||||
|  * | ||||
|  * | ||||
|  * On PA-RISC, this is the Network part of LASI chip. | ||||
|  * See: | ||||
|  * https://parisc.wiki.kernel.org/images-parisc/7/79/Lasi_ers.pdf
 | ||||
|  */ | ||||
| 
 | ||||
| #include "qemu/osdep.h" | ||||
| #include "qemu/timer.h" | ||||
| #include "hw/sysbus.h" | ||||
| #include "net/eth.h" | ||||
| #include "hw/net/lasi_82596.h" | ||||
| #include "hw/net/i82596.h" | ||||
| #include "trace.h" | ||||
| #include "sysemu/sysemu.h" | ||||
| #include "hw/qdev-properties.h" | ||||
| #include "migration/vmstate.h" | ||||
| 
 | ||||
| #define PA_I82596_RESET         0       /* Offsets relative to LASI-LAN-Addr.*/ | ||||
| #define PA_CPU_PORT_L_ACCESS    4 | ||||
| #define PA_CHANNEL_ATTENTION    8 | ||||
| #define PA_GET_MACADDR          12 | ||||
| 
 | ||||
| #define SWAP32(x)   (((uint32_t)(x) << 16) | ((((uint32_t)(x))) >> 16)) | ||||
| 
 | ||||
| static void lasi_82596_mem_write(void *opaque, hwaddr addr, | ||||
|                             uint64_t val, unsigned size) | ||||
| { | ||||
|     SysBusI82596State *d = opaque; | ||||
| 
 | ||||
|     trace_lasi_82596_mem_writew(addr, val); | ||||
|     switch (addr) { | ||||
|     case PA_I82596_RESET: | ||||
|         i82596_h_reset(&d->state); | ||||
|         break; | ||||
|     case PA_CPU_PORT_L_ACCESS: | ||||
|         d->val_index++; | ||||
|         if (d->val_index == 0) { | ||||
|             uint32_t v = d->last_val | (val << 16); | ||||
|             v = v & ~0xff; | ||||
|             i82596_ioport_writew(&d->state, d->last_val & 0xff, v); | ||||
|         } | ||||
|         d->last_val = val; | ||||
|         break; | ||||
|     case PA_CHANNEL_ATTENTION: | ||||
|         i82596_ioport_writew(&d->state, PORT_CA, val); | ||||
|         break; | ||||
|     case PA_GET_MACADDR: | ||||
|         /*
 | ||||
|          * Provided for SeaBIOS only. Write MAC of Network card to addr @val. | ||||
|          * Needed for the PDC_LAN_STATION_ID_READ PDC call. | ||||
|          */ | ||||
|         address_space_rw(&address_space_memory, val, | ||||
|             MEMTXATTRS_UNSPECIFIED, d->state.conf.macaddr.a, ETH_ALEN, 1); | ||||
|         break; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static uint64_t lasi_82596_mem_read(void *opaque, hwaddr addr, | ||||
|                                unsigned size) | ||||
| { | ||||
|     SysBusI82596State *d = opaque; | ||||
|     uint32_t val; | ||||
| 
 | ||||
|     if (addr == PA_GET_MACADDR) { | ||||
|         val = 0xBEEFBABE; | ||||
|     } else { | ||||
|         val = i82596_ioport_readw(&d->state, addr); | ||||
|     } | ||||
|     trace_lasi_82596_mem_readw(addr, val); | ||||
|     return val; | ||||
| } | ||||
| 
 | ||||
| static const MemoryRegionOps lasi_82596_mem_ops = { | ||||
|     .read = lasi_82596_mem_read, | ||||
|     .write = lasi_82596_mem_write, | ||||
|     .endianness = DEVICE_BIG_ENDIAN, | ||||
|     .valid = { | ||||
|         .min_access_size = 4, | ||||
|         .max_access_size = 4, | ||||
|     }, | ||||
| }; | ||||
| 
 | ||||
| static NetClientInfo net_lasi_82596_info = { | ||||
|     .type = NET_CLIENT_DRIVER_NIC, | ||||
|     .size = sizeof(NICState), | ||||
|     .can_receive = i82596_can_receive, | ||||
|     .receive = i82596_receive, | ||||
|     .link_status_changed = i82596_set_link_status, | ||||
| }; | ||||
| 
 | ||||
| static const VMStateDescription vmstate_lasi_82596 = { | ||||
|     .name = "i82596", | ||||
|     .version_id = 1, | ||||
|     .minimum_version_id = 1, | ||||
|     .fields = (VMStateField[]) { | ||||
|         VMSTATE_STRUCT(state, SysBusI82596State, 0, vmstate_i82596, | ||||
|                 I82596State), | ||||
|         VMSTATE_END_OF_LIST() | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| static void lasi_82596_realize(DeviceState *dev, Error **errp) | ||||
| { | ||||
|     SysBusI82596State *d = SYSBUS_I82596(dev); | ||||
|     I82596State *s = &d->state; | ||||
| 
 | ||||
|     memory_region_init_io(&s->mmio, OBJECT(d), &lasi_82596_mem_ops, d, | ||||
|                 "lasi_82596-mmio", PA_GET_MACADDR + 4); | ||||
| 
 | ||||
|     i82596_common_init(dev, s, &net_lasi_82596_info); | ||||
| } | ||||
| 
 | ||||
| SysBusI82596State *lasi_82596_init(MemoryRegion *addr_space, | ||||
|                   hwaddr hpa, qemu_irq lan_irq) | ||||
| { | ||||
|     DeviceState *dev; | ||||
|     SysBusI82596State *s; | ||||
|     static const MACAddr HP_MAC = { | ||||
|         .a = { 0x08, 0x00, 0x09, 0xef, 0x34, 0xf6 } }; | ||||
| 
 | ||||
|     qemu_check_nic_model(&nd_table[0], TYPE_LASI_82596); | ||||
|     dev = qdev_create(NULL, TYPE_LASI_82596); | ||||
|     s = SYSBUS_I82596(dev); | ||||
|     s->state.irq = lan_irq; | ||||
|     qdev_set_nic_properties(dev, &nd_table[0]); | ||||
|     qdev_init_nofail(dev); | ||||
|     s->state.conf.macaddr = HP_MAC; /* set HP MAC prefix */ | ||||
| 
 | ||||
|     /* LASI 82596 ports in main memory. */ | ||||
|     memory_region_add_subregion(addr_space, hpa, &s->state.mmio); | ||||
|     return s; | ||||
| } | ||||
| 
 | ||||
| static void lasi_82596_reset(DeviceState *dev) | ||||
| { | ||||
|     SysBusI82596State *d = SYSBUS_I82596(dev); | ||||
| 
 | ||||
|     i82596_h_reset(&d->state); | ||||
| } | ||||
| 
 | ||||
| static void lasi_82596_instance_init(Object *obj) | ||||
| { | ||||
|     SysBusI82596State *d = SYSBUS_I82596(obj); | ||||
|     I82596State *s = &d->state; | ||||
| 
 | ||||
|     device_add_bootindex_property(obj, &s->conf.bootindex, | ||||
|                                   "bootindex", "/ethernet-phy@0", | ||||
|                                   DEVICE(obj), NULL); | ||||
| } | ||||
| 
 | ||||
| static Property lasi_82596_properties[] = { | ||||
|     DEFINE_NIC_PROPERTIES(SysBusI82596State, state.conf), | ||||
|     DEFINE_PROP_END_OF_LIST(), | ||||
| }; | ||||
| 
 | ||||
| static void lasi_82596_class_init(ObjectClass *klass, void *data) | ||||
| { | ||||
|     DeviceClass *dc = DEVICE_CLASS(klass); | ||||
| 
 | ||||
|     dc->realize = lasi_82596_realize; | ||||
|     set_bit(DEVICE_CATEGORY_NETWORK, dc->categories); | ||||
|     dc->fw_name = "ethernet"; | ||||
|     dc->reset = lasi_82596_reset; | ||||
|     dc->vmsd = &vmstate_lasi_82596; | ||||
|     dc->user_creatable = false; | ||||
|     device_class_set_props(dc, lasi_82596_properties); | ||||
| } | ||||
| 
 | ||||
| static const TypeInfo lasi_82596_info = { | ||||
|     .name          = TYPE_LASI_82596, | ||||
|     .parent        = TYPE_SYS_BUS_DEVICE, | ||||
|     .instance_size = sizeof(SysBusI82596State), | ||||
|     .class_init    = lasi_82596_class_init, | ||||
|     .instance_init = lasi_82596_instance_init, | ||||
| }; | ||||
| 
 | ||||
| static void lasi_82596_register_types(void) | ||||
| { | ||||
|     type_register_static(&lasi_82596_info); | ||||
| } | ||||
| 
 | ||||
| type_init(lasi_82596_register_types) | ||||
| @ -381,3 +381,16 @@ tulip_mii_read(int phy, int reg, uint16_t data) "phy 0x%x, reg 0x%x data 0x%04x" | ||||
| tulip_reset(void) "" | ||||
| tulip_setup_frame(void) "" | ||||
| tulip_setup_filter(int n, uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f) "%d: %02x:%02x:%02x:%02x:%02x:%02x" | ||||
| 
 | ||||
| # lasi_i82596.c | ||||
| lasi_82596_mem_readw(uint64_t addr, uint32_t ret) "addr=0x%"PRIx64" val=0x%04x" | ||||
| lasi_82596_mem_writew(uint64_t addr, uint32_t val) "addr=0x%"PRIx64" val=0x%04x" | ||||
| 
 | ||||
| # i82596.c | ||||
| i82596_s_reset(void *s) "%p Reset chip" | ||||
| i82596_transmit(uint32_t size, uint32_t addr) "size %u from addr 0x%04x" | ||||
| i82596_receive_analysis(const char *s) "%s" | ||||
| i82596_receive_packet(size_t sz) "len=%zu" | ||||
| i82596_new_mac(const char *id_with_mac) "New MAC for: %s" | ||||
| i82596_set_multicast(uint16_t count) "Added %d multicast entries" | ||||
| i82596_channel_attention(void *s) "%p: Received CHANNEL ATTENTION" | ||||
|  | ||||
							
								
								
									
										16
									
								
								include/hw/input/lasips2.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								include/hw/input/lasips2.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| /*
 | ||||
|  * QEMU LASI PS/2 emulation | ||||
|  * | ||||
|  * Copyright (c) 2019 Sven Schnelle | ||||
|  * | ||||
|  */ | ||||
| #ifndef HW_INPUT_LASIPS2_H | ||||
| #define HW_INPUT_LASIPS2_H | ||||
| 
 | ||||
| #include "exec/hwaddr.h" | ||||
| 
 | ||||
| #define TYPE_LASIPS2 "lasips2" | ||||
| 
 | ||||
| void lasips2_init(MemoryRegion *address_space, hwaddr base, qemu_irq irq); | ||||
| 
 | ||||
| #endif /* HW_INPUT_LASIPS2_H */ | ||||
| @ -47,5 +47,6 @@ void ps2_queue_3(PS2State *s, int b1, int b2, int b3); | ||||
| void ps2_queue_4(PS2State *s, int b1, int b2, int b3, int b4); | ||||
| void ps2_keyboard_set_translation(void *opaque, int mode); | ||||
| void ps2_mouse_fake_event(void *opaque); | ||||
| int ps2_queue_empty(PS2State *s); | ||||
| 
 | ||||
| #endif /* HW_PS2_H */ | ||||
|  | ||||
							
								
								
									
										29
									
								
								include/hw/net/lasi_82596.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								include/hw/net/lasi_82596.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| /*
 | ||||
|  * QEMU LASI i82596 device emulation | ||||
|  * | ||||
|  * Copyright (c) 201 Helge Deller <deller@gmx.de> | ||||
|  * | ||||
|  */ | ||||
| 
 | ||||
| #ifndef LASI_82596_H | ||||
| #define LASI_82596_H | ||||
| 
 | ||||
| #include "net/net.h" | ||||
| #include "hw/net/i82596.h" | ||||
| 
 | ||||
| #define TYPE_LASI_82596 "lasi_82596" | ||||
| #define SYSBUS_I82596(obj) \ | ||||
|     OBJECT_CHECK(SysBusI82596State, (obj), TYPE_LASI_82596) | ||||
| 
 | ||||
| typedef struct { | ||||
|     SysBusDevice parent_obj; | ||||
| 
 | ||||
|     I82596State state; | ||||
|     uint16_t last_val; | ||||
|     int val_index:1; | ||||
| } SysBusI82596State; | ||||
| 
 | ||||
| SysBusI82596State *lasi_82596_init(MemoryRegion *addr_space, | ||||
|                                     hwaddr hpa, qemu_irq irq); | ||||
| 
 | ||||
| #endif | ||||
										
											Binary file not shown.
										
									
								
							| @ -1 +1 @@ | ||||
| Subproject commit 0f4fe84658165e96ce35870fd19fc634e182e77b | ||||
| Subproject commit 1630ac7d65c4a09218cc677f1fa56cd5b3140447 | ||||
| @ -17,6 +17,8 @@ DEF_HELPER_FLAGS_3(stby_b_parallel, TCG_CALL_NO_WG, void, env, tl, tr) | ||||
| DEF_HELPER_FLAGS_3(stby_e, TCG_CALL_NO_WG, void, env, tl, tr) | ||||
| DEF_HELPER_FLAGS_3(stby_e_parallel, TCG_CALL_NO_WG, void, env, tl, tr) | ||||
| 
 | ||||
| DEF_HELPER_FLAGS_1(ldc_check, TCG_CALL_NO_RWG, void, tl) | ||||
| 
 | ||||
| DEF_HELPER_FLAGS_4(probe, TCG_CALL_NO_WG, tr, env, tl, i32, i32) | ||||
| 
 | ||||
| DEF_HELPER_FLAGS_1(loaded_fr0, TCG_CALL_NO_RWG, void, env) | ||||
|  | ||||
| @ -153,6 +153,15 @@ void HELPER(stby_e_parallel)(CPUHPPAState *env, target_ulong addr, | ||||
|     do_stby_e(env, addr, val, true, GETPC()); | ||||
| } | ||||
| 
 | ||||
| void HELPER(ldc_check)(target_ulong addr) | ||||
| { | ||||
|     if (unlikely(addr & 0xf)) { | ||||
|         qemu_log_mask(LOG_GUEST_ERROR, | ||||
|                       "Undefined ldc to unaligned address mod 16: " | ||||
|                       TARGET_FMT_lx "\n", addr); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| target_ureg HELPER(probe)(CPUHPPAState *env, target_ulong addr, | ||||
|                           uint32_t level, uint32_t want) | ||||
| { | ||||
|  | ||||
| @ -2942,7 +2942,7 @@ static bool trans_st(DisasContext *ctx, arg_ldst *a) | ||||
| 
 | ||||
| static bool trans_ldc(DisasContext *ctx, arg_ldst *a) | ||||
| { | ||||
|     MemOp mop = MO_TEUL | MO_ALIGN_16 | a->size; | ||||
|     MemOp mop = MO_TE | MO_ALIGN | a->size; | ||||
|     TCGv_reg zero, dest, ofs; | ||||
|     TCGv_tl addr; | ||||
| 
 | ||||
| @ -2958,8 +2958,21 @@ static bool trans_ldc(DisasContext *ctx, arg_ldst *a) | ||||
| 
 | ||||
|     form_gva(ctx, &addr, &ofs, a->b, a->x, a->scale ? a->size : 0, | ||||
|              a->disp, a->sp, a->m, ctx->mmu_idx == MMU_PHYS_IDX); | ||||
| 
 | ||||
|     /*
 | ||||
|      * For hppa1.1, LDCW is undefined unless aligned mod 16. | ||||
|      * However actual hardware succeeds with aligned mod 4. | ||||
|      * Detect this case and log a GUEST_ERROR. | ||||
|      * | ||||
|      * TODO: HPPA64 relaxes the over-alignment requirement | ||||
|      * with the ,co completer. | ||||
|      */ | ||||
|     gen_helper_ldc_check(addr); | ||||
| 
 | ||||
|     zero = tcg_const_reg(0); | ||||
|     tcg_gen_atomic_xchg_reg(dest, addr, zero, ctx->mmu_idx, mop); | ||||
|     tcg_temp_free(zero); | ||||
| 
 | ||||
|     if (a->m) { | ||||
|         save_gpr(ctx, a->b, ofs); | ||||
|     } | ||||
|  | ||||
| @ -135,7 +135,8 @@ static testdef_t tests[] = { | ||||
|       sizeof(kernel_plml605), kernel_plml605 }, | ||||
|     { "moxie", "moxiesim", "", "TT", sizeof(bios_moxiesim), 0, bios_moxiesim }, | ||||
|     { "arm", "raspi2", "", "TT", sizeof(bios_raspi2), 0, bios_raspi2 }, | ||||
|     { "hppa", "hppa", "", "SeaBIOS wants SYSTEM HALT" }, | ||||
|     /* For hppa, force bios to output to serial by disabling graphics. */ | ||||
|     { "hppa", "hppa", "-vga none", "SeaBIOS wants SYSTEM HALT" }, | ||||
|     { "aarch64", "virt", "-cpu cortex-a57", "TT", sizeof(kernel_aarch64), | ||||
|       kernel_aarch64 }, | ||||
|     { "arm", "microbit", "", "T", sizeof(kernel_nrf51), kernel_nrf51 }, | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Peter Maydell
						Peter Maydell