1/*
2 * Copyright 2019-2022, Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Augustin Cavalier <waddlesplash>
7 */
8
9#include <debug.h>
10#include <kernel/vm/vm.h>
11#include <PCI.h>
12
13extern "C" {
14#include "nvme.h"
15#include "nvme_log.h"
16#include "nvme_mem.h"
17#include "nvme_pci.h"
18}
19
20
21static pci_module_info* sPCIModule = NULL;
22
23
24// #pragma mark - memory
25
26
27int
28nvme_mem_init()
29{
30	/* nothing to do */
31	return 0;
32}
33
34
35void
36nvme_mem_cleanup()
37{
38	/* nothing to do */
39}
40
41
42void*
43nvme_mem_alloc_node(size_t size, size_t align, unsigned int node_id,
44	phys_addr_t* paddr)
45{
46	size = ROUNDUP(size, B_PAGE_SIZE);
47
48	virtual_address_restrictions virtualRestrictions = {};
49
50	physical_address_restrictions physicalRestrictions = {};
51	physicalRestrictions.alignment = align;
52
53	void* address;
54	area_id area = create_area_etc(B_SYSTEM_TEAM, "nvme physical buffer",
55		size, B_CONTIGUOUS, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA,
56		0, 0, &virtualRestrictions, &physicalRestrictions, &address);
57	if (area < 0)
58		return NULL;
59
60	if (paddr != NULL)
61		*paddr = nvme_mem_vtophys(address);
62	return address;
63}
64
65
66void*
67nvme_malloc_node(size_t size, size_t align, unsigned int node_id)
68{
69	return nvme_mem_alloc_node(size, align, node_id, NULL);
70}
71
72
73void
74nvme_free(void* addr)
75{
76	delete_area(area_for(addr));
77}
78
79
80phys_addr_t
81nvme_mem_vtophys(void* vaddr)
82{
83	physical_entry entry;
84	status_t status = get_memory_map((void*)vaddr, 1, &entry, 1);
85	if (status != B_OK) {
86		panic("nvme: get_memory_map failed for %p: %s\n",
87			(void*)vaddr, strerror(status));
88		return NVME_VTOPHYS_ERROR;
89	}
90
91	return entry.address;
92}
93
94
95// #pragma mark - PCI
96
97
98int
99nvme_pci_init()
100{
101	status_t status = get_module(B_PCI_MODULE_NAME,
102		(module_info**)&sPCIModule);
103	return status;
104}
105
106
107int
108nvme_pcicfg_read32(struct pci_device* dev, uint32_t* value, uint32_t offset)
109{
110	*value = sPCIModule->read_pci_config(dev->bus, dev->dev, dev->func, offset,
111		sizeof(*value));
112	return 0;
113}
114
115
116int
117nvme_pcicfg_write32(struct pci_device* dev, uint32_t value, uint32_t offset)
118{
119	sPCIModule->write_pci_config(dev->bus, dev->dev, dev->func, offset,
120		sizeof(value), value);
121	return 0;
122}
123
124
125void
126nvme_pcicfg_get_bar_addr_len(void* devhandle, unsigned int bar,
127	uint64_t* _addr, uint64_t* _size)
128{
129	struct pci_device* dev = (struct pci_device*)devhandle;
130	pci_info* info = (pci_info*)dev->pci_info;
131
132	uint64 addr = info->u.h0.base_registers[bar];
133	uint64 size = info->u.h0.base_register_sizes[bar];
134	if ((info->u.h0.base_register_flags[bar] & PCI_address_type) == PCI_address_type_64) {
135		addr |= (uint64)info->u.h0.base_registers[bar + 1] << 32;
136		size |= (uint64)info->u.h0.base_register_sizes[bar + 1] << 32;
137	}
138
139	*_addr = addr;
140	*_size = size;
141}
142
143
144int
145nvme_pcicfg_map_bar(void* devhandle, unsigned int bar, bool read_only,
146	void** mapped_addr)
147{
148	uint64 addr, size;
149	nvme_pcicfg_get_bar_addr_len(devhandle, bar, &addr, &size);
150
151	area_id area = map_physical_memory("nvme mapped bar", (phys_addr_t)addr, (size_t)size,
152		B_ANY_KERNEL_ADDRESS, B_KERNEL_READ_AREA | (read_only ? 0 : B_KERNEL_WRITE_AREA),
153		mapped_addr);
154	if (area < B_OK)
155		return area;
156
157	return 0;
158}
159
160
161int
162nvme_pcicfg_map_bar_write_combine(void* devhandle, unsigned int bar,
163	void** mapped_addr)
164{
165	status_t status = nvme_pcicfg_map_bar(devhandle, bar, false, mapped_addr);
166	if (status != 0)
167		return status;
168
169	// Turn on write combining for the area
170	status = vm_set_area_memory_type(area_for(*mapped_addr),
171		nvme_mem_vtophys(*mapped_addr), B_MTR_WC);
172	if (status != 0)
173		nvme_pcicfg_unmap_bar(devhandle, bar, *mapped_addr);
174	return status;
175}
176
177
178int
179nvme_pcicfg_unmap_bar(void* devhandle, unsigned int bar, void* addr)
180{
181	return delete_area(area_for(addr));
182}
183
184
185// #pragma mark - logging
186
187
188void
189nvme_log(enum nvme_log_level level, const char *format, ...)
190{
191	va_list ap;
192
193	va_start(ap, format);
194	nvme_vlog(level, format, ap);
195	va_end(ap);
196}
197
198
199void
200nvme_vlog(enum nvme_log_level level, const char *format, va_list ap)
201{
202	dvprintf(format, ap);
203}
204