diff --git a/sys/dev/drm2/drm_os_freebsd.h b/sys/dev/drm2/drm_os_freebsd.h index 71a9637ddd9f..ec1042f8f0d4 100644 --- a/sys/dev/drm2/drm_os_freebsd.h +++ b/sys/dev/drm2/drm_os_freebsd.h @@ -1,696 +1,689 @@ /** * \file drm_os_freebsd.h * OS abstraction macros. */ #include #ifndef _DRM_OS_FREEBSD_H_ #define _DRM_OS_FREEBSD_H_ #include #include #if _BYTE_ORDER == _BIG_ENDIAN #define __BIG_ENDIAN 4321 #else #define __LITTLE_ENDIAN 1234 #endif #ifdef __LP64__ #define BITS_PER_LONG 64 #else #define BITS_PER_LONG 32 #endif #ifndef __user #define __user #endif #ifndef __iomem #define __iomem #endif #ifndef __always_unused #define __always_unused #endif #ifndef __must_check #define __must_check #endif #ifndef __force #define __force #endif #ifndef uninitialized_var #define uninitialized_var(x) x #endif #define cpu_to_le16(x) htole16(x) #define le16_to_cpu(x) le16toh(x) #define cpu_to_le32(x) htole32(x) #define le32_to_cpu(x) le32toh(x) #define cpu_to_be16(x) htobe16(x) #define be16_to_cpu(x) be16toh(x) #define cpu_to_be32(x) htobe32(x) #define be32_to_cpu(x) be32toh(x) #define be32_to_cpup(x) be32toh(*x) typedef vm_paddr_t dma_addr_t; typedef vm_paddr_t resource_size_t; #define wait_queue_head_t atomic_t typedef uint64_t u64; typedef uint32_t u32; typedef uint16_t u16; typedef uint8_t u8; typedef int64_t s64; typedef int32_t s32; typedef int16_t s16; typedef int8_t s8; typedef uint16_t __le16; typedef uint32_t __le32; typedef uint64_t __le64; typedef uint16_t __be16; typedef uint32_t __be32; typedef uint64_t __be64; #define DRM_IRQ_ARGS void *arg typedef void irqreturn_t; #define IRQ_HANDLED /* nothing */ #define IRQ_NONE /* nothing */ #define __init #define __exit #define BUILD_BUG_ON(x) CTASSERT(!(x)) #define BUILD_BUG_ON_NOT_POWER_OF_2(x) #ifndef WARN #define WARN(condition, format, ...) ({ \ int __ret_warn_on = !!(condition); \ if (unlikely(__ret_warn_on)) \ DRM_ERROR(format, ##__VA_ARGS__); \ unlikely(__ret_warn_on); \ }) #endif #define WARN_ONCE(condition, format, ...) \ WARN(condition, format, ##__VA_ARGS__) #define WARN_ON(cond) WARN(cond, "WARN ON: " #cond) #define WARN_ON_SMP(cond) WARN_ON(cond) #define BUG() panic("BUG") #define BUG_ON(cond) KASSERT(!(cond), ("BUG ON: " #cond " -> 0x%jx", (uintmax_t)(cond))) #define unlikely(x) __builtin_expect(!!(x), 0) #define likely(x) __builtin_expect(!!(x), 1) #define container_of(ptr, type, member) ({ \ __typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) #define KHZ2PICOS(a) (1000000000UL/(a)) #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) #define HZ hz #define DRM_HZ hz #define DRM_CURRENTPID curthread->td_proc->p_pid #define DRM_SUSER(p) (priv_check(p, PRIV_DRIVER) == 0) #define udelay(usecs) DELAY(usecs) #define mdelay(msecs) do { int loops = (msecs); \ while (loops--) DELAY(1000); \ } while (0) #define DRM_UDELAY(udelay) DELAY(udelay) #define drm_msleep(x, msg) pause((msg), ((int64_t)(x)) * hz / 1000) #define DRM_MSLEEP(msecs) drm_msleep((msecs), "drm_msleep") #define get_seconds() time_second #define ioread8(addr) *(volatile uint8_t *)((char *)addr) #define ioread16(addr) *(volatile uint16_t *)((char *)addr) #define ioread32(addr) *(volatile uint32_t *)((char *)addr) #define iowrite8(data, addr) *(volatile uint8_t *)((char *)addr) = data; #define iowrite16(data, addr) *(volatile uint16_t *)((char *)addr) = data; #define iowrite32(data, addr) *(volatile uint32_t *)((char *)addr) = data; #define DRM_READ8(map, offset) \ *(volatile u_int8_t *)(((vm_offset_t)(map)->handle) + \ (vm_offset_t)(offset)) #define DRM_READ16(map, offset) \ le16toh(*(volatile u_int16_t *)(((vm_offset_t)(map)->handle) + \ (vm_offset_t)(offset))) #define DRM_READ32(map, offset) \ le32toh(*(volatile u_int32_t *)(((vm_offset_t)(map)->handle) + \ (vm_offset_t)(offset))) #define DRM_READ64(map, offset) \ le64toh(*(volatile u_int64_t *)(((vm_offset_t)(map)->handle) + \ (vm_offset_t)(offset))) #define DRM_WRITE8(map, offset, val) \ *(volatile u_int8_t *)(((vm_offset_t)(map)->handle) + \ (vm_offset_t)(offset)) = val #define DRM_WRITE16(map, offset, val) \ *(volatile u_int16_t *)(((vm_offset_t)(map)->handle) + \ (vm_offset_t)(offset)) = htole16(val) #define DRM_WRITE32(map, offset, val) \ *(volatile u_int32_t *)(((vm_offset_t)(map)->handle) + \ (vm_offset_t)(offset)) = htole32(val) #define DRM_WRITE64(map, offset, val) \ *(volatile u_int64_t *)(((vm_offset_t)(map)->handle) + \ (vm_offset_t)(offset)) = htole64(val) #if !defined(__arm__) #if defined(__i386__) || defined(__amd64__) || defined(__powerpc__) || defined(__aarch64__) -#define DRM_MSG "This code is deprecated. Install the graphics/drm-kmod pkg\n" +#define DRM_MSG "WARNING! drm2 module is deprecated. Install the graphics/drm-kmod pkg\n" #else -#define DRM_MSG "This code is deprecated." +#define DRM_MSG "WARNING! drm2 module is deprecated.\n" #endif - -#define DRM_OBSOLETE(dev) \ - do { \ - device_printf(dev, "=======================================================\n"); \ - device_printf(dev, DRM_MSG); \ - device_printf(dev, "=======================================================\n"); \ - gone_in_dev(dev, 13, "drm2 drivers"); \ - } while (0) +#define DRM_OBSOLETE(dev) gone_in_dev(dev, 13, DRM_MSG) #endif /* __arm__ */ /* DRM_READMEMORYBARRIER() prevents reordering of reads. * DRM_WRITEMEMORYBARRIER() prevents reordering of writes. * DRM_MEMORYBARRIER() prevents reordering of reads and writes. */ #define DRM_READMEMORYBARRIER() rmb() #define DRM_WRITEMEMORYBARRIER() wmb() #define DRM_MEMORYBARRIER() mb() #define smp_rmb() rmb() #define smp_wmb() wmb() #define smp_mb__before_atomic_inc() mb() #define smp_mb__after_atomic_inc() mb() #define barrier() __compiler_membar() #define do_div(a, b) ((a) /= (b)) #define div64_u64(a, b) ((a) / (b)) #define lower_32_bits(n) ((u32)(n)) #define upper_32_bits(n) ((u32)(((n) >> 16) >> 16)) #define __set_bit(n, s) set_bit((n), (s)) #define __clear_bit(n, s) clear_bit((n), (s)) #define min_t(type, x, y) ({ \ type __min1 = (x); \ type __min2 = (y); \ __min1 < __min2 ? __min1 : __min2; }) #define max_t(type, x, y) ({ \ type __max1 = (x); \ type __max2 = (y); \ __max1 > __max2 ? __max1 : __max2; }) #define memset_io(a, b, c) memset((a), (b), (c)) #define memcpy_fromio(a, b, c) memcpy((a), (b), (c)) #define memcpy_toio(a, b, c) memcpy((a), (b), (c)) #define VERIFY_READ VM_PROT_READ #define VERIFY_WRITE VM_PROT_WRITE #define access_ok(prot, p, l) useracc((p), (l), (prot)) /* XXXKIB what is the right code for the FreeBSD ? */ /* kib@ used ENXIO here -- dumbbell@ */ #define EREMOTEIO EIO #define ERESTARTSYS 512 /* Same value as Linux. */ #define KTR_DRM KTR_DEV #define KTR_DRM_REG KTR_SPARE3 #define DRM_AGP_KERN struct agp_info #define DRM_AGP_MEM void #define PCI_VENDOR_ID_APPLE 0x106b #define PCI_VENDOR_ID_ASUSTEK 0x1043 #define PCI_VENDOR_ID_ATI 0x1002 #define PCI_VENDOR_ID_DELL 0x1028 #define PCI_VENDOR_ID_HP 0x103c #define PCI_VENDOR_ID_IBM 0x1014 #define PCI_VENDOR_ID_INTEL 0x8086 #define PCI_VENDOR_ID_SERVERWORKS 0x1166 #define PCI_VENDOR_ID_SONY 0x104d #define PCI_VENDOR_ID_VIA 0x1106 #define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d)) #define DIV_ROUND_CLOSEST(n,d) (((n) + (d) / 2) / (d)) #define div_u64(n, d) ((n) / (d)) #define hweight32(i) bitcount32(i) /** * ror32 - rotate a 32-bit value right * @word: value to rotate * @shift: bits to roll * * Source: include/linux/bitops.h */ static inline uint32_t ror32(uint32_t word, unsigned int shift) { return (word >> shift) | (word << (32 - shift)); } #define IS_ALIGNED(x, y) (((x) & ((y) - 1)) == 0) #define round_down(x, y) rounddown2((x), (y)) #define round_up(x, y) roundup2((x), (y)) #define get_unaligned(ptr) \ ({ __typeof__(*(ptr)) __tmp; \ memcpy(&__tmp, (ptr), sizeof(*(ptr))); __tmp; }) #if _BYTE_ORDER == _LITTLE_ENDIAN /* Taken from linux/include/linux/unaligned/le_struct.h. */ struct __una_u32 { u32 x; } __packed; static inline u32 __get_unaligned_cpu32(const void *p) { const struct __una_u32 *ptr = (const struct __una_u32 *)p; return (ptr->x); } static inline u32 get_unaligned_le32(const void *p) { return (__get_unaligned_cpu32((const u8 *)p)); } #else /* Taken from linux/include/linux/unaligned/le_byteshift.h. */ static inline u32 __get_unaligned_le32(const u8 *p) { return (p[0] | p[1] << 8 | p[2] << 16 | p[3] << 24); } static inline u32 get_unaligned_le32(const void *p) { return (__get_unaligned_le32((const u8 *)p)); } #endif int64_t timeval_to_ns(const struct timeval *tv); struct timeval ns_to_timeval(const int64_t nsec); #define PAGE_ALIGN(addr) round_page(addr) #define page_to_phys(x) VM_PAGE_TO_PHYS(x) #define offset_in_page(x) ((x) & PAGE_MASK) #define drm_get_device_from_kdev(_kdev) (((struct drm_minor *)(_kdev)->si_drv1)->dev) #define DRM_IOC_VOID IOC_VOID #define DRM_IOC_READ IOC_OUT #define DRM_IOC_WRITE IOC_IN #define DRM_IOC_READWRITE IOC_INOUT #define DRM_IOC(dir, group, nr, size) _IOC(dir, group, nr, size) static inline long __copy_to_user(void __user *to, const void *from, unsigned long n) { return (copyout(from, to, n) != 0 ? n : 0); } #define copy_to_user(to, from, n) __copy_to_user((to), (from), (n)) static inline int __put_user(size_t size, void *ptr, void *x) { size = copy_to_user(ptr, x, size); return (size ? -EFAULT : size); } #define put_user(x, ptr) __put_user(sizeof(*ptr), (ptr), &(x)) static inline unsigned long __copy_from_user(void *to, const void __user *from, unsigned long n) { return ((copyin(__DECONST(void *, from), to, n) != 0 ? n : 0)); } #define copy_from_user(to, from, n) __copy_from_user((to), (from), (n)) static inline int __get_user(size_t size, const void *ptr, void *x) { size = copy_from_user(x, ptr, size); return (size ? -EFAULT : size); } #define get_user(x, ptr) __get_user(sizeof(*ptr), (ptr), &(x)) static inline int __copy_to_user_inatomic(void __user *to, const void *from, unsigned n) { return (copyout_nofault(from, to, n) != 0 ? n : 0); } #define __copy_to_user_inatomic_nocache(to, from, n) \ __copy_to_user_inatomic((to), (from), (n)) static inline unsigned long __copy_from_user_inatomic(void *to, const void __user *from, unsigned long n) { /* * XXXKIB. Equivalent Linux function is implemented using * MOVNTI for aligned moves. For unaligned head and tail, * normal move is performed. As such, it is not incorrect, if * only somewhat slower, to use normal copyin. All uses * except shmem_pwrite_fast() have the destination mapped WC. */ return ((copyin_nofault(__DECONST(void *, from), to, n) != 0 ? n : 0)); } #define __copy_from_user_inatomic_nocache(to, from, n) \ __copy_from_user_inatomic((to), (from), (n)) static inline int fault_in_multipages_readable(const char __user *uaddr, int size) { char c; int ret = 0; const char __user *end = uaddr + size - 1; if (unlikely(size == 0)) return ret; while (uaddr <= end) { ret = -copyin(uaddr, &c, 1); if (ret != 0) return -EFAULT; uaddr += PAGE_SIZE; } /* Check whether the range spilled into the next page. */ if (((unsigned long)uaddr & ~PAGE_MASK) == ((unsigned long)end & ~PAGE_MASK)) { ret = -copyin(end, &c, 1); } return ret; } static inline int fault_in_multipages_writeable(char __user *uaddr, int size) { int ret = 0; char __user *end = uaddr + size - 1; if (unlikely(size == 0)) return ret; /* * Writing zeroes into userspace here is OK, because we know that if * the zero gets there, we'll be overwriting it. */ while (uaddr <= end) { ret = subyte(uaddr, 0); if (ret != 0) return -EFAULT; uaddr += PAGE_SIZE; } /* Check whether the range spilled into the next page. */ if (((unsigned long)uaddr & ~PAGE_MASK) == ((unsigned long)end & ~PAGE_MASK)) ret = subyte(end, 0); return ret; } enum __drm_capabilities { CAP_SYS_ADMIN }; static inline bool capable(enum __drm_capabilities cap) { switch (cap) { case CAP_SYS_ADMIN: return DRM_SUSER(curthread); default: panic("%s: unhandled capability: %0x", __func__, cap); return (false); } } #define to_user_ptr(x) ((void *)(uintptr_t)(x)) #define sigemptyset(set) SIGEMPTYSET(set) #define sigaddset(set, sig) SIGADDSET(set, sig) #define DRM_LOCK(dev) sx_xlock(&(dev)->dev_struct_lock) #define DRM_UNLOCK(dev) sx_xunlock(&(dev)->dev_struct_lock) extern unsigned long drm_linux_timer_hz_mask; #define jiffies ticks #define jiffies_to_msecs(x) (((int64_t)(x)) * 1000 / hz) #define msecs_to_jiffies(x) (((int64_t)(x)) * hz / 1000) #define time_after(a,b) ((long)(b) - (long)(a) < 0) #define time_after_eq(a,b) ((long)(b) - (long)(a) <= 0) #define round_jiffies(j) ((unsigned long)(((j) + drm_linux_timer_hz_mask) & ~drm_linux_timer_hz_mask)) #define round_jiffies_up(j) round_jiffies(j) /* TODO */ #define round_jiffies_up_relative(j) round_jiffies_up(j) /* TODO */ #define getrawmonotonic(ts) getnanouptime(ts) #define wake_up(queue) wakeup_one((void *)queue) #define wake_up_interruptible(queue) wakeup_one((void *)queue) #define wake_up_all(queue) wakeup((void *)queue) #define wake_up_interruptible_all(queue) wakeup((void *)queue) struct completion { unsigned int done; struct mtx lock; }; #define INIT_COMPLETION(c) ((c).done = 0); static inline void init_completion(struct completion *c) { mtx_init(&c->lock, "drmcompl", NULL, MTX_DEF); c->done = 0; } static inline void free_completion(struct completion *c) { mtx_destroy(&c->lock); } static inline void complete_all(struct completion *c) { mtx_lock(&c->lock); c->done++; mtx_unlock(&c->lock); wakeup(c); } static inline long wait_for_completion_interruptible_timeout(struct completion *c, unsigned long timeout) { unsigned long start_jiffies, elapsed_jiffies; bool timeout_expired = false, awakened = false; long ret = timeout; start_jiffies = ticks; mtx_lock(&c->lock); while (c->done == 0 && !timeout_expired) { ret = -msleep(c, &c->lock, PCATCH, "drmwco", timeout); switch(ret) { case -EWOULDBLOCK: timeout_expired = true; ret = 0; break; case -EINTR: case -ERESTART: ret = -ERESTARTSYS; break; case 0: awakened = true; break; } } mtx_unlock(&c->lock); if (awakened) { elapsed_jiffies = ticks - start_jiffies; ret = timeout > elapsed_jiffies ? timeout - elapsed_jiffies : 1; } return (ret); } MALLOC_DECLARE(DRM_MEM_DMA); MALLOC_DECLARE(DRM_MEM_SAREA); MALLOC_DECLARE(DRM_MEM_DRIVER); MALLOC_DECLARE(DRM_MEM_MAGIC); MALLOC_DECLARE(DRM_MEM_MINOR); MALLOC_DECLARE(DRM_MEM_IOCTLS); MALLOC_DECLARE(DRM_MEM_MAPS); MALLOC_DECLARE(DRM_MEM_BUFS); MALLOC_DECLARE(DRM_MEM_SEGS); MALLOC_DECLARE(DRM_MEM_PAGES); MALLOC_DECLARE(DRM_MEM_FILES); MALLOC_DECLARE(DRM_MEM_QUEUES); MALLOC_DECLARE(DRM_MEM_CMDS); MALLOC_DECLARE(DRM_MEM_MAPPINGS); MALLOC_DECLARE(DRM_MEM_BUFLISTS); MALLOC_DECLARE(DRM_MEM_AGPLISTS); MALLOC_DECLARE(DRM_MEM_CTXBITMAP); MALLOC_DECLARE(DRM_MEM_SGLISTS); MALLOC_DECLARE(DRM_MEM_MM); MALLOC_DECLARE(DRM_MEM_HASHTAB); MALLOC_DECLARE(DRM_MEM_KMS); MALLOC_DECLARE(DRM_MEM_VBLANK); #define simple_strtol(a, b, c) strtol((a), (b), (c)) typedef struct drm_pci_id_list { int vendor; int device; long driver_private; char *name; } drm_pci_id_list_t; #ifdef __i386__ #define CONFIG_X86 1 #endif #ifdef __amd64__ #define CONFIG_X86 1 #define CONFIG_X86_64 1 #endif #ifdef __ia64__ #define CONFIG_IA64 1 #endif #if defined(__i386__) || defined(__amd64__) #define CONFIG_ACPI #define CONFIG_DRM_I915_KMS #undef CONFIG_INTEL_IOMMU #endif #ifdef COMPAT_FREEBSD32 #define CONFIG_COMPAT #endif #ifndef __arm__ #define CONFIG_AGP 1 #define CONFIG_MTRR 1 #endif #define CONFIG_FB 1 extern const char *fb_mode_option; #undef CONFIG_DEBUG_FS #undef CONFIG_VGA_CONSOLE #define EXPORT_SYMBOL(x) #define EXPORT_SYMBOL_GPL(x) #define MODULE_AUTHOR(author) #define MODULE_DESCRIPTION(desc) #define MODULE_LICENSE(license) #define MODULE_PARM_DESC(name, desc) #define MODULE_DEVICE_TABLE(name, list) #define module_param_named(name, var, type, perm) #define printk printf #define pr_err DRM_ERROR #define pr_warn DRM_WARNING #define pr_warn_once DRM_WARNING #define KERN_DEBUG "" /* I2C compatibility. */ #define I2C_M_RD IIC_M_RD #define I2C_M_WR IIC_M_WR #define I2C_M_NOSTART IIC_M_NOSTART struct fb_info * framebuffer_alloc(void); void framebuffer_release(struct fb_info *info); #define console_lock() #define console_unlock() #define console_trylock() true #define PM_EVENT_SUSPEND 0x0002 #define PM_EVENT_QUIESCE 0x0008 #define PM_EVENT_PRETHAW PM_EVENT_QUIESCE typedef struct pm_message { int event; } pm_message_t; static inline int pci_read_config_byte(device_t kdev, int where, u8 *val) { *val = (u8)pci_read_config(kdev, where, 1); return (0); } static inline int pci_write_config_byte(device_t kdev, int where, u8 val) { pci_write_config(kdev, where, val, 1); return (0); } static inline int pci_read_config_word(device_t kdev, int where, uint16_t *val) { *val = (uint16_t)pci_read_config(kdev, where, 2); return (0); } static inline int pci_write_config_word(device_t kdev, int where, uint16_t val) { pci_write_config(kdev, where, val, 2); return (0); } static inline int pci_read_config_dword(device_t kdev, int where, uint32_t *val) { *val = (uint32_t)pci_read_config(kdev, where, 4); return (0); } static inline int pci_write_config_dword(device_t kdev, int where, uint32_t val) { pci_write_config(kdev, where, val, 4); return (0); } static inline void on_each_cpu(void callback(void *data), void *data, int wait) { smp_rendezvous(NULL, callback, NULL, data); } void hex_dump_to_buffer(const void *buf, size_t len, int rowsize, int groupsize, char *linebuf, size_t linebuflen, bool ascii); #define KIB_NOTYET() \ do { \ if (drm_debug && drm_notyet) \ printf("NOTYET: %s at %s:%d\n", __func__, __FILE__, __LINE__); \ } while (0) #endif /* _DRM_OS_FREEBSD_H_ */ diff --git a/sys/dev/le/lance.c b/sys/dev/le/lance.c index 6eef8b5da052..f5e41e82bb42 100644 --- a/sys/dev/le/lance.c +++ b/sys/dev/le/lance.c @@ -1,817 +1,818 @@ /* $NetBSD: lance.c,v 1.34 2005/12/24 20:27:30 perry Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1997, 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Charles M. Hannum and by Jason R. Thorpe of the Numerical Aerospace * Simulation Facility, NASA Ames Research Center. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /*- * Copyright (c) 1992, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Ralph Campbell and Rick Macklem. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void lance_start(if_t); static void lance_stop(struct lance_softc *); static void lance_init(void *); static void lance_watchdog(void *s); static int lance_mediachange(if_t); static void lance_mediastatus(if_t, struct ifmediareq *); static int lance_ioctl(if_t, u_long, caddr_t); int lance_config(struct lance_softc *sc, const char* name, int unit) { if_t ifp; int i, nbuf; if (LE_LOCK_INITIALIZED(sc) == 0) return (ENXIO); ifp = sc->sc_ifp = if_alloc(IFT_ETHER); callout_init_mtx(&sc->sc_wdog_ch, &sc->sc_mtx, 0); /* Initialize ifnet structure. */ if_setsoftc(ifp, sc); if_initname(ifp, name, unit); if_setstartfn(ifp, lance_start); if_setioctlfn(ifp, lance_ioctl); if_setinitfn(ifp, lance_init); if_setflags(ifp, IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST); #ifdef LANCE_REVC_BUG if_setflagsbit(ifp, 0, IFF_MULTICAST); #endif if_setbaudrate(ifp, IF_Mbps(10)); if_setsendqlen(ifp, ifqmaxlen); if_setsendqready(ifp); /* Initialize ifmedia structures. */ ifmedia_init(&sc->sc_media, 0, lance_mediachange, lance_mediastatus); if (sc->sc_supmedia != NULL) { for (i = 0; i < sc->sc_nsupmedia; i++) ifmedia_add(&sc->sc_media, sc->sc_supmedia[i], 0, NULL); ifmedia_set(&sc->sc_media, sc->sc_defaultmedia); } else { ifmedia_add(&sc->sc_media, IFM_MAKEWORD(IFM_ETHER, IFM_MANUAL, 0, 0), 0, NULL); ifmedia_set(&sc->sc_media, IFM_MAKEWORD(IFM_ETHER, IFM_MANUAL, 0, 0)); } switch (sc->sc_memsize) { case 8192: sc->sc_nrbuf = 4; sc->sc_ntbuf = 1; break; case 16384: sc->sc_nrbuf = 8; sc->sc_ntbuf = 2; break; case 32768: sc->sc_nrbuf = 16; sc->sc_ntbuf = 4; break; case 65536: sc->sc_nrbuf = 32; sc->sc_ntbuf = 8; break; case 131072: sc->sc_nrbuf = 64; sc->sc_ntbuf = 16; break; case 262144: sc->sc_nrbuf = 128; sc->sc_ntbuf = 32; break; default: /* weird memory size; cope with it */ nbuf = sc->sc_memsize / LEBLEN; sc->sc_ntbuf = nbuf / 5; sc->sc_nrbuf = nbuf - sc->sc_ntbuf; } if_printf(ifp, "%d receive buffers, %d transmit buffers\n", sc->sc_nrbuf, sc->sc_ntbuf); /* Make sure the chip is stopped. */ LE_LOCK(sc); lance_stop(sc); LE_UNLOCK(sc); return (0); } void lance_attach(struct lance_softc *sc) { if_t ifp = sc->sc_ifp; /* Attach the interface. */ ether_ifattach(ifp, sc->sc_enaddr); /* Claim 802.1q capability. */ if_setifheaderlen(ifp, sizeof(struct ether_vlan_header)); if_setcapabilitiesbit(ifp, IFCAP_VLAN_MTU, 0); if_setcapenablebit(ifp, IFCAP_VLAN_MTU, 0); - gone_in(15, "le: 10/100 NIC no longer needed for Qemu/MIPS"); + gone_in(15, "Warning! le(4) to be removed: no longer needed for " + "Qemu/MIPS\n"); } void lance_detach(struct lance_softc *sc) { if_t ifp = sc->sc_ifp; LE_LOCK(sc); lance_stop(sc); LE_UNLOCK(sc); callout_drain(&sc->sc_wdog_ch); ether_ifdetach(ifp); if_free(ifp); } void lance_suspend(struct lance_softc *sc) { LE_LOCK(sc); lance_stop(sc); LE_UNLOCK(sc); } void lance_resume(struct lance_softc *sc) { LE_LOCK(sc); if (if_getflags(sc->sc_ifp) & IFF_UP) lance_init_locked(sc); LE_UNLOCK(sc); } static void lance_start(if_t ifp) { struct lance_softc *sc = if_getsoftc(ifp); LE_LOCK(sc); (*sc->sc_start_locked)(sc); LE_UNLOCK(sc); } static void lance_stop(struct lance_softc *sc) { if_t ifp = sc->sc_ifp; LE_LOCK_ASSERT(sc, MA_OWNED); /* * Mark the interface down and cancel the watchdog timer. */ if_setdrvflagbits(ifp, 0, (IFF_DRV_RUNNING | IFF_DRV_OACTIVE)); callout_stop(&sc->sc_wdog_ch); sc->sc_wdog_timer = 0; (*sc->sc_wrcsr)(sc, LE_CSR0, LE_C0_STOP); } static void lance_init(void *xsc) { struct lance_softc *sc = (struct lance_softc *)xsc; LE_LOCK(sc); lance_init_locked(sc); LE_UNLOCK(sc); } /* * Initialization of interface; set up initialization block * and transmit/receive descriptor rings. */ void lance_init_locked(struct lance_softc *sc) { if_t ifp = sc->sc_ifp; u_long a; int timo; LE_LOCK_ASSERT(sc, MA_OWNED); (*sc->sc_wrcsr)(sc, LE_CSR0, LE_C0_STOP); DELAY(100); /* Newer LANCE chips have a reset register. */ if (sc->sc_hwreset) (*sc->sc_hwreset)(sc); /* Set the correct byte swapping mode, etc. */ (*sc->sc_wrcsr)(sc, LE_CSR3, sc->sc_conf3); /* Set the current media. This may require the chip to be stopped. */ if (sc->sc_mediachange) (void)(*sc->sc_mediachange)(sc); /* * Update our private copy of the Ethernet address. * We NEED the copy so we can ensure its alignment! */ memcpy(sc->sc_enaddr, if_getlladdr(ifp), ETHER_ADDR_LEN); /* Set up LANCE init block. */ (*sc->sc_meminit)(sc); /* Give LANCE the physical address of its init block. */ a = sc->sc_addr + LE_INITADDR(sc); (*sc->sc_wrcsr)(sc, LE_CSR1, a & 0xffff); (*sc->sc_wrcsr)(sc, LE_CSR2, a >> 16); /* Try to initialize the LANCE. */ DELAY(100); (*sc->sc_wrcsr)(sc, LE_CSR0, LE_C0_INIT); /* Wait for initialization to finish. */ for (timo = 100000; timo; timo--) if ((*sc->sc_rdcsr)(sc, LE_CSR0) & LE_C0_IDON) break; if ((*sc->sc_rdcsr)(sc, LE_CSR0) & LE_C0_IDON) { /* Start the LANCE. */ (*sc->sc_wrcsr)(sc, LE_CSR0, LE_C0_INEA | LE_C0_STRT); if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0); if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE); sc->sc_wdog_timer = 0; callout_reset(&sc->sc_wdog_ch, hz, lance_watchdog, sc); (*sc->sc_start_locked)(sc); } else if_printf(ifp, "controller failed to initialize\n"); if (sc->sc_hwinit) (*sc->sc_hwinit)(sc); } /* * Routine to copy from mbuf chain to transmit buffer in * network buffer memory. */ int lance_put(struct lance_softc *sc, int boff, struct mbuf *m) { struct mbuf *n; int len, tlen = 0; LE_LOCK_ASSERT(sc, MA_OWNED); for (; m; m = n) { len = m->m_len; if (len == 0) { n = m_free(m); m = NULL; continue; } (*sc->sc_copytobuf)(sc, mtod(m, caddr_t), boff, len); boff += len; tlen += len; n = m_free(m); m = NULL; } if (tlen < LEMINSIZE) { (*sc->sc_zerobuf)(sc, boff, LEMINSIZE - tlen); tlen = LEMINSIZE; } return (tlen); } /* * Pull data off an interface. * Len is length of data, with local net header stripped. * We copy the data into mbufs. When full cluster sized units are present * we copy into clusters. */ struct mbuf * lance_get(struct lance_softc *sc, int boff, int totlen) { if_t ifp = sc->sc_ifp; struct mbuf *m, *m0, *newm; caddr_t newdata; int len; if (totlen <= ETHER_HDR_LEN || totlen > LEBLEN - ETHER_CRC_LEN) { #ifdef LEDEBUG if_printf(ifp, "invalid packet size %d; dropping\n", totlen); #endif return (NULL); } MGETHDR(m0, M_NOWAIT, MT_DATA); if (m0 == NULL) return (NULL); m0->m_pkthdr.rcvif = ifp; m0->m_pkthdr.len = totlen; len = MHLEN; m = m0; while (totlen > 0) { if (totlen >= MINCLSIZE) { if (!(MCLGET(m, M_NOWAIT))) goto bad; len = MCLBYTES; } if (m == m0) { newdata = (caddr_t) ALIGN(m->m_data + ETHER_HDR_LEN) - ETHER_HDR_LEN; len -= newdata - m->m_data; m->m_data = newdata; } m->m_len = len = min(totlen, len); (*sc->sc_copyfrombuf)(sc, mtod(m, caddr_t), boff, len); boff += len; totlen -= len; if (totlen > 0) { MGET(newm, M_NOWAIT, MT_DATA); if (newm == NULL) goto bad; len = MLEN; m = m->m_next = newm; } } return (m0); bad: m_freem(m0); return (NULL); } static void lance_watchdog(void *xsc) { struct lance_softc *sc = (struct lance_softc *)xsc; if_t ifp = sc->sc_ifp; LE_LOCK_ASSERT(sc, MA_OWNED); if (sc->sc_wdog_timer == 0 || --sc->sc_wdog_timer != 0) { callout_reset(&sc->sc_wdog_ch, hz, lance_watchdog, sc); return; } if_printf(ifp, "device timeout\n"); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); lance_init_locked(sc); } static int lance_mediachange(if_t ifp) { struct lance_softc *sc = if_getsoftc(ifp); if (sc->sc_mediachange) { /* * For setting the port in LE_CSR15 the PCnet chips must * be powered down or stopped and unlike documented may * not take effect without an initialization. So don't * invoke (*sc_mediachange) directly here but go through * lance_init_locked(). */ LE_LOCK(sc); lance_stop(sc); lance_init_locked(sc); if (!if_sendq_empty(ifp)) (*sc->sc_start_locked)(sc); LE_UNLOCK(sc); } return (0); } static void lance_mediastatus(if_t ifp, struct ifmediareq *ifmr) { struct lance_softc *sc = if_getsoftc(ifp); LE_LOCK(sc); if (!(if_getflags(ifp) & IFF_UP)) { LE_UNLOCK(sc); return; } ifmr->ifm_status = IFM_AVALID; if (sc->sc_flags & LE_CARRIER) ifmr->ifm_status |= IFM_ACTIVE; if (sc->sc_mediastatus) (*sc->sc_mediastatus)(sc, ifmr); LE_UNLOCK(sc); } /* * Process an ioctl request. */ static int lance_ioctl(if_t ifp, u_long cmd, caddr_t data) { struct lance_softc *sc = if_getsoftc(ifp); struct ifreq *ifr = (struct ifreq *)data; int error = 0; switch (cmd) { case SIOCSIFFLAGS: LE_LOCK(sc); if (if_getflags(ifp) & IFF_PROMISC) { if (!(sc->sc_flags & LE_PROMISC)) { sc->sc_flags |= LE_PROMISC; lance_init_locked(sc); } } else if (sc->sc_flags & LE_PROMISC) { sc->sc_flags &= ~LE_PROMISC; lance_init_locked(sc); } if ((if_getflags(ifp) & IFF_ALLMULTI) && !(sc->sc_flags & LE_ALLMULTI)) { sc->sc_flags |= LE_ALLMULTI; lance_init_locked(sc); } else if (!(if_getflags(ifp) & IFF_ALLMULTI) && (sc->sc_flags & LE_ALLMULTI)) { sc->sc_flags &= ~LE_ALLMULTI; lance_init_locked(sc); } if (!(if_getflags(ifp) & IFF_UP) && if_getdrvflags(ifp) & IFF_DRV_RUNNING) { /* * If interface is marked down and it is running, then * stop it. */ lance_stop(sc); } else if (if_getflags(ifp) & IFF_UP && !(if_getdrvflags(ifp) & IFF_DRV_RUNNING)) { /* * If interface is marked up and it is stopped, then * start it. */ lance_init_locked(sc); } #ifdef LEDEBUG if (if_getflags(ifp) & IFF_DEBUG) sc->sc_flags |= LE_DEBUG; else sc->sc_flags &= ~LE_DEBUG; #endif LE_UNLOCK(sc); break; case SIOCADDMULTI: case SIOCDELMULTI: /* * Multicast list has changed; set the hardware filter * accordingly. */ LE_LOCK(sc); if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) lance_init_locked(sc); LE_UNLOCK(sc); break; case SIOCGIFMEDIA: case SIOCSIFMEDIA: error = ifmedia_ioctl(ifp, ifr, &sc->sc_media, cmd); break; default: error = ether_ioctl(ifp, cmd, data); break; } return (error); } struct lance_hash_maddr_ctx { struct lance_softc *sc; uint16_t *af; }; static u_int lance_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt) { struct lance_hash_maddr_ctx *ctx = arg; struct lance_softc *sc = ctx->sc; uint32_t crc; crc = ether_crc32_le(LLADDR(sdl), ETHER_ADDR_LEN); /* Just want the 6 most significant bits. */ crc >>= 26; /* Set the corresponding bit in the filter. */ ctx->af[crc >> 4] |= LE_HTOLE16(1 << (crc & 0xf)); return (1); } /* * Set up the logical address filter. */ void lance_setladrf(struct lance_softc *sc, uint16_t *af) { if_t ifp = sc->sc_ifp; struct lance_hash_maddr_ctx ctx = { sc, af }; /* * Set up multicast address filter by passing all multicast addresses * through a crc generator, and then using the high order 6 bits as an * index into the 64 bit logical address filter. The high order bit * selects the word, while the rest of the bits select the bit within * the word. */ if (if_getflags(ifp) & IFF_PROMISC || sc->sc_flags & LE_ALLMULTI) { af[0] = af[1] = af[2] = af[3] = 0xffff; return; } af[0] = af[1] = af[2] = af[3] = 0x0000; if_foreach_llmaddr(ifp, lance_hash_maddr, &ctx); } /* * Routines for accessing the transmit and receive buffers. * The various CPU and adapter configurations supported by this * driver require three different access methods for buffers * and descriptors: * (1) contig (contiguous data; no padding), * (2) gap2 (two bytes of data followed by two bytes of padding), * (3) gap16 (16 bytes of data followed by 16 bytes of padding). */ /* * contig: contiguous data with no padding. * * Buffers may have any alignment. */ void lance_copytobuf_contig(struct lance_softc *sc, void *from, int boff, int len) { volatile caddr_t buf = sc->sc_mem; /* * Just call memcpy() to do the work. */ memcpy(buf + boff, from, len); } void lance_copyfrombuf_contig(struct lance_softc *sc, void *to, int boff, int len) { volatile caddr_t buf = sc->sc_mem; /* * Just call memcpy() to do the work. */ memcpy(to, buf + boff, len); } void lance_zerobuf_contig(struct lance_softc *sc, int boff, int len) { volatile caddr_t buf = sc->sc_mem; /* * Just let memset() do the work */ memset(buf + boff, 0, len); } #if 0 /* * Examples only; duplicate these and tweak (if necessary) in * machine-specific front-ends. */ /* * gap2: two bytes of data followed by two bytes of pad. * * Buffers must be 4-byte aligned. The code doesn't worry about * doing an extra byte. */ static void lance_copytobuf_gap2(struct lance_softc *sc, void *fromv, int boff, int len) { volatile caddr_t buf = sc->sc_mem; caddr_t from = fromv; volatile uint16_t *bptr; if (boff & 0x1) { /* Handle unaligned first byte. */ bptr = ((volatile uint16_t *)buf) + (boff - 1); *bptr = (*from++ << 8) | (*bptr & 0xff); bptr += 2; len--; } else bptr = ((volatile uint16_t *)buf) + boff; while (len > 1) { *bptr = (from[1] << 8) | (from[0] & 0xff); bptr += 2; from += 2; len -= 2; } if (len == 1) *bptr = (uint16_t)*from; } static void lance_copyfrombuf_gap2(struct lance_softc *sc, void *tov, int boff, int len) { volatile caddr_t buf = sc->sc_mem; caddr_t to = tov; volatile uint16_t *bptr; uint16_t tmp; if (boff & 0x1) { /* Handle unaligned first byte. */ bptr = ((volatile uint16_t *)buf) + (boff - 1); *to++ = (*bptr >> 8) & 0xff; bptr += 2; len--; } else bptr = ((volatile uint16_t *)buf) + boff; while (len > 1) { tmp = *bptr; *to++ = tmp & 0xff; *to++ = (tmp >> 8) & 0xff; bptr += 2; len -= 2; } if (len == 1) *to = *bptr & 0xff; } static void lance_zerobuf_gap2(struct lance_softc *sc, int boff, int len) { volatile caddr_t buf = sc->sc_mem; volatile uint16_t *bptr; if ((unsigned)boff & 0x1) { bptr = ((volatile uint16_t *)buf) + (boff - 1); *bptr &= 0xff; bptr += 2; len--; } else bptr = ((volatile uint16_t *)buf) + boff; while (len > 0) { *bptr = 0; bptr += 2; len -= 2; } } /* * gap16: 16 bytes of data followed by 16 bytes of pad. * * Buffers must be 32-byte aligned. */ static void lance_copytobuf_gap16(struct lance_softc *sc, void *fromv, int boff, int len) { volatile caddr_t buf = sc->sc_mem; caddr_t bptr, from = fromv; int xfer; bptr = buf + ((boff << 1) & ~0x1f); boff &= 0xf; xfer = min(len, 16 - boff); while (len > 0) { memcpy(bptr + boff, from, xfer); from += xfer; bptr += 32; boff = 0; len -= xfer; xfer = min(len, 16); } } static void lance_copyfrombuf_gap16(struct lance_softc *sc, void *tov, int boff, int len) { volatile caddr_t buf = sc->sc_mem; caddr_t bptr, to = tov; int xfer; bptr = buf + ((boff << 1) & ~0x1f); boff &= 0xf; xfer = min(len, 16 - boff); while (len > 0) { memcpy(to, bptr + boff, xfer); to += xfer; bptr += 32; boff = 0; len -= xfer; xfer = min(len, 16); } } static void lance_zerobuf_gap16(struct lance_softc *sc, int boff, int len) { volatile caddr_t buf = sc->sc_mem; caddr_t bptr; int xfer; bptr = buf + ((boff << 1) & ~0x1f); boff &= 0xf; xfer = min(len, 16 - boff); while (len > 0) { memset(bptr + boff, 0, xfer); bptr += 32; boff = 0; len -= xfer; xfer = min(len, 16); } } #endif /* Example only */ diff --git a/sys/dev/sound/midi/sequencer.c b/sys/dev/sound/midi/sequencer.c index db49aa1ef259..03b71688175c 100644 --- a/sys/dev/sound/midi/sequencer.c +++ b/sys/dev/sound/midi/sequencer.c @@ -1,2105 +1,2107 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2003 Mathew Kanner * Copyright (c) 1993 Hannu Savolainen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * The sequencer personality manager. */ #include #include #include #include #include #include #include #include #include #include /* for DATA_SET */ #include #include #include #include #include #include #include #include #include #include #include /* for DELAY */ #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include #include #include "synth_if.h" #include #define TMR_TIMERBASE 13 #define SND_DEV_SEQ 1 /* Sequencer output /dev/sequencer (FM * synthesizer and MIDI output) */ #define SND_DEV_MUSIC 8 /* /dev/music, level 2 interface */ /* Length of a sequencer event. */ #define EV_SZ 8 #define IEV_SZ 8 /* Lookup modes */ #define LOOKUP_EXIST (0) #define LOOKUP_OPEN (1) #define LOOKUP_CLOSE (2) #define MIDIDEV(y) (dev2unit(y) & 0x0f) /* These are the entries to the sequencer driver. */ static d_open_t mseq_open; static d_close_t mseq_close; static d_ioctl_t mseq_ioctl; static d_read_t mseq_read; static d_write_t mseq_write; static d_poll_t mseq_poll; static struct cdevsw seq_cdevsw = { .d_version = D_VERSION, .d_open = mseq_open, .d_close = mseq_close, .d_read = mseq_read, .d_write = mseq_write, .d_ioctl = mseq_ioctl, .d_poll = mseq_poll, .d_name = "sequencer", }; struct seq_softc { KOBJ_FIELDS; struct mtx seq_lock, q_lock; struct cv empty_cv, reset_cv, in_cv, out_cv, state_cv, th_cv; MIDIQ_HEAD(, u_char) in_q, out_q; u_long flags; /* Flags (protected by flag_mtx of mididev_info) */ int fflags; /* Access mode */ int music; int out_water; /* Sequence output threshould */ snd_sync_parm sync_parm; /* AIOSYNC parameter set */ struct thread *sync_thread; /* AIOSYNCing thread */ struct selinfo in_sel, out_sel; int midi_number; struct cdev *seqdev, *musicdev; int unit; int maxunits; kobj_t *midis; int *midi_flags; kobj_t mapper; void *mapper_cookie; struct timeval timerstop, timersub; int timerbase, tempo; int timerrun; int done; int playing; int recording; int busy; int pre_event_timeout; int waiting; }; /* * Module specific stuff, including how many sequecers * we currently own. */ SYSCTL_NODE(_hw_midi, OID_AUTO, seq, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "Midi sequencer"); int seq_debug; /* XXX: should this be moved into debug.midi? */ SYSCTL_INT(_hw_midi_seq, OID_AUTO, debug, CTLFLAG_RW, &seq_debug, 0, ""); midi_cmdtab cmdtab_seqevent[] = { {SEQ_NOTEOFF, "SEQ_NOTEOFF"}, {SEQ_NOTEON, "SEQ_NOTEON"}, {SEQ_WAIT, "SEQ_WAIT"}, {SEQ_PGMCHANGE, "SEQ_PGMCHANGE"}, {SEQ_SYNCTIMER, "SEQ_SYNCTIMER"}, {SEQ_MIDIPUTC, "SEQ_MIDIPUTC"}, {SEQ_DRUMON, "SEQ_DRUMON"}, {SEQ_DRUMOFF, "SEQ_DRUMOFF"}, {SEQ_ECHO, "SEQ_ECHO"}, {SEQ_AFTERTOUCH, "SEQ_AFTERTOUCH"}, {SEQ_CONTROLLER, "SEQ_CONTROLLER"}, {SEQ_BALANCE, "SEQ_BALANCE"}, {SEQ_VOLMODE, "SEQ_VOLMODE"}, {SEQ_FULLSIZE, "SEQ_FULLSIZE"}, {SEQ_PRIVATE, "SEQ_PRIVATE"}, {SEQ_EXTENDED, "SEQ_EXTENDED"}, {EV_SEQ_LOCAL, "EV_SEQ_LOCAL"}, {EV_TIMING, "EV_TIMING"}, {EV_CHN_COMMON, "EV_CHN_COMMON"}, {EV_CHN_VOICE, "EV_CHN_VOICE"}, {EV_SYSEX, "EV_SYSEX"}, {-1, NULL}, }; midi_cmdtab cmdtab_seqioctl[] = { {SNDCTL_SEQ_RESET, "SNDCTL_SEQ_RESET"}, {SNDCTL_SEQ_SYNC, "SNDCTL_SEQ_SYNC"}, {SNDCTL_SYNTH_INFO, "SNDCTL_SYNTH_INFO"}, {SNDCTL_SEQ_CTRLRATE, "SNDCTL_SEQ_CTRLRATE"}, {SNDCTL_SEQ_GETOUTCOUNT, "SNDCTL_SEQ_GETOUTCOUNT"}, {SNDCTL_SEQ_GETINCOUNT, "SNDCTL_SEQ_GETINCOUNT"}, {SNDCTL_SEQ_PERCMODE, "SNDCTL_SEQ_PERCMODE"}, {SNDCTL_FM_LOAD_INSTR, "SNDCTL_FM_LOAD_INSTR"}, {SNDCTL_SEQ_TESTMIDI, "SNDCTL_SEQ_TESTMIDI"}, {SNDCTL_SEQ_RESETSAMPLES, "SNDCTL_SEQ_RESETSAMPLES"}, {SNDCTL_SEQ_NRSYNTHS, "SNDCTL_SEQ_NRSYNTHS"}, {SNDCTL_SEQ_NRMIDIS, "SNDCTL_SEQ_NRMIDIS"}, {SNDCTL_SEQ_GETTIME, "SNDCTL_SEQ_GETTIME"}, {SNDCTL_MIDI_INFO, "SNDCTL_MIDI_INFO"}, {SNDCTL_SEQ_THRESHOLD, "SNDCTL_SEQ_THRESHOLD"}, {SNDCTL_SYNTH_MEMAVL, "SNDCTL_SYNTH_MEMAVL"}, {SNDCTL_FM_4OP_ENABLE, "SNDCTL_FM_4OP_ENABLE"}, {SNDCTL_PMGR_ACCESS, "SNDCTL_PMGR_ACCESS"}, {SNDCTL_SEQ_PANIC, "SNDCTL_SEQ_PANIC"}, {SNDCTL_SEQ_OUTOFBAND, "SNDCTL_SEQ_OUTOFBAND"}, {SNDCTL_TMR_TIMEBASE, "SNDCTL_TMR_TIMEBASE"}, {SNDCTL_TMR_START, "SNDCTL_TMR_START"}, {SNDCTL_TMR_STOP, "SNDCTL_TMR_STOP"}, {SNDCTL_TMR_CONTINUE, "SNDCTL_TMR_CONTINUE"}, {SNDCTL_TMR_TEMPO, "SNDCTL_TMR_TEMPO"}, {SNDCTL_TMR_SOURCE, "SNDCTL_TMR_SOURCE"}, {SNDCTL_TMR_METRONOME, "SNDCTL_TMR_METRONOME"}, {SNDCTL_TMR_SELECT, "SNDCTL_TMR_SELECT"}, {SNDCTL_MIDI_PRETIME, "SNDCTL_MIDI_PRETIME"}, {AIONWRITE, "AIONWRITE"}, {AIOGSIZE, "AIOGSIZE"}, {AIOSSIZE, "AIOSSIZE"}, {AIOGFMT, "AIOGFMT"}, {AIOSFMT, "AIOSFMT"}, {AIOGMIX, "AIOGMIX"}, {AIOSMIX, "AIOSMIX"}, {AIOSTOP, "AIOSTOP"}, {AIOSYNC, "AIOSYNC"}, {AIOGCAP, "AIOGCAP"}, {-1, NULL}, }; midi_cmdtab cmdtab_timer[] = { {TMR_WAIT_REL, "TMR_WAIT_REL"}, {TMR_WAIT_ABS, "TMR_WAIT_ABS"}, {TMR_STOP, "TMR_STOP"}, {TMR_START, "TMR_START"}, {TMR_CONTINUE, "TMR_CONTINUE"}, {TMR_TEMPO, "TMR_TEMPO"}, {TMR_ECHO, "TMR_ECHO"}, {TMR_CLOCK, "TMR_CLOCK"}, {TMR_SPP, "TMR_SPP"}, {TMR_TIMESIG, "TMR_TIMESIG"}, {-1, NULL}, }; midi_cmdtab cmdtab_seqcv[] = { {MIDI_NOTEOFF, "MIDI_NOTEOFF"}, {MIDI_NOTEON, "MIDI_NOTEON"}, {MIDI_KEY_PRESSURE, "MIDI_KEY_PRESSURE"}, {-1, NULL}, }; midi_cmdtab cmdtab_seqccmn[] = { {MIDI_CTL_CHANGE, "MIDI_CTL_CHANGE"}, {MIDI_PGM_CHANGE, "MIDI_PGM_CHANGE"}, {MIDI_CHN_PRESSURE, "MIDI_CHN_PRESSURE"}, {MIDI_PITCH_BEND, "MIDI_PITCH_BEND"}, {MIDI_SYSTEM_PREFIX, "MIDI_SYSTEM_PREFIX"}, {-1, NULL}, }; #ifndef KOBJMETHOD_END #define KOBJMETHOD_END { NULL, NULL } #endif /* * static const char *mpu401_mprovider(kobj_t obj, struct mpu401 *m); */ static kobj_method_t seq_methods[] = { /* KOBJMETHOD(mpu_provider,mpu401_mprovider), */ KOBJMETHOD_END }; DEFINE_CLASS(sequencer, seq_methods, 0); /* The followings are the local function. */ static int seq_convertold(u_char *event, u_char *out); /* * static void seq_midiinput(struct seq_softc * scp, void *md); */ static void seq_reset(struct seq_softc *scp); static int seq_sync(struct seq_softc *scp); static int seq_processevent(struct seq_softc *scp, u_char *event); static int seq_timing(struct seq_softc *scp, u_char *event); static int seq_local(struct seq_softc *scp, u_char *event); static int seq_chnvoice(struct seq_softc *scp, kobj_t md, u_char *event); static int seq_chncommon(struct seq_softc *scp, kobj_t md, u_char *event); static int seq_sysex(struct seq_softc *scp, kobj_t md, u_char *event); static int seq_fetch_mid(struct seq_softc *scp, int unit, kobj_t *md); void seq_copytoinput(struct seq_softc *scp, u_char *event, int len); int seq_modevent(module_t mod, int type, void *data); struct seq_softc *seqs[10]; static struct mtx seqinfo_mtx; static u_long nseq = 0; static void timer_start(struct seq_softc *t); static void timer_stop(struct seq_softc *t); static void timer_setvals(struct seq_softc *t, int tempo, int timerbase); static void timer_wait(struct seq_softc *t, int ticks, int wait_abs); static int timer_now(struct seq_softc *t); static void timer_start(struct seq_softc *t) { t->timerrun = 1; getmicrotime(&t->timersub); } static void timer_continue(struct seq_softc *t) { struct timeval now; if (t->timerrun == 1) return; t->timerrun = 1; getmicrotime(&now); timevalsub(&now, &t->timerstop); timevaladd(&t->timersub, &now); } static void timer_stop(struct seq_softc *t) { t->timerrun = 0; getmicrotime(&t->timerstop); } static void timer_setvals(struct seq_softc *t, int tempo, int timerbase) { t->tempo = tempo; t->timerbase = timerbase; } static void timer_wait(struct seq_softc *t, int ticks, int wait_abs) { struct timeval now, when; int ret; unsigned long long i; while (t->timerrun == 0) { SEQ_DEBUG(2, printf("Timer wait when timer isn't running\n")); /* * The old sequencer used timeouts that only increased * the timer when the timer was running. * Hence the sequencer would stick (?) if the * timer was disabled. */ cv_wait(&t->reset_cv, &t->seq_lock); if (t->playing == 0) return; } i = ticks * 60ull * 1000000ull / (t->tempo * t->timerbase); when.tv_sec = i / 1000000; when.tv_usec = i % 1000000; #if 0 printf("timer_wait tempo %d timerbase %d ticks %d abs %d u_sec %llu\n", t->tempo, t->timerbase, ticks, wait_abs, i); #endif if (wait_abs != 0) { getmicrotime(&now); timevalsub(&now, &t->timersub); timevalsub(&when, &now); } if (when.tv_sec < 0 || when.tv_usec < 0) { SEQ_DEBUG(3, printf("seq_timer error negative time %lds.%06lds\n", (long)when.tv_sec, (long)when.tv_usec)); return; } i = when.tv_sec * 1000000ull; i += when.tv_usec; i *= hz; i /= 1000000ull; #if 0 printf("seq_timer usec %llu ticks %llu\n", when.tv_sec * 1000000ull + when.tv_usec, i); #endif t->waiting = 1; ret = cv_timedwait(&t->reset_cv, &t->seq_lock, i + 1); t->waiting = 0; if (ret != EWOULDBLOCK) SEQ_DEBUG(3, printf("seq_timer didn't timeout\n")); } static int timer_now(struct seq_softc *t) { struct timeval now; unsigned long long i; int ret; if (t->timerrun == 0) now = t->timerstop; else getmicrotime(&now); timevalsub(&now, &t->timersub); i = now.tv_sec * 1000000ull; i += now.tv_usec; i *= t->timerbase; /* i /= t->tempo; */ i /= 1000000ull; ret = i; /* * printf("timer_now: %llu %d\n", i, ret); */ return ret; } static void seq_eventthread(void *arg) { struct seq_softc *scp = arg; u_char event[EV_SZ]; mtx_lock(&scp->seq_lock); SEQ_DEBUG(2, printf("seq_eventthread started\n")); while (scp->done == 0) { restart: while (scp->playing == 0) { cv_wait(&scp->state_cv, &scp->seq_lock); if (scp->done) goto done; } while (MIDIQ_EMPTY(scp->out_q)) { cv_broadcast(&scp->empty_cv); cv_wait(&scp->out_cv, &scp->seq_lock); if (scp->playing == 0) goto restart; if (scp->done) goto done; } MIDIQ_DEQ(scp->out_q, event, EV_SZ); if (MIDIQ_AVAIL(scp->out_q) < scp->out_water) { cv_broadcast(&scp->out_cv); selwakeup(&scp->out_sel); } seq_processevent(scp, event); } done: cv_broadcast(&scp->th_cv); mtx_unlock(&scp->seq_lock); SEQ_DEBUG(2, printf("seq_eventthread finished\n")); kproc_exit(0); } /* * seq_processevent: This maybe called by the event thread or the IOCTL * handler for queued and out of band events respectively. */ static int seq_processevent(struct seq_softc *scp, u_char *event) { int ret; kobj_t m; ret = 0; if (event[0] == EV_SEQ_LOCAL) ret = seq_local(scp, event); else if (event[0] == EV_TIMING) ret = seq_timing(scp, event); else if (event[0] != EV_CHN_VOICE && event[0] != EV_CHN_COMMON && event[0] != EV_SYSEX && event[0] != SEQ_MIDIPUTC) { ret = 1; SEQ_DEBUG(2, printf("seq_processevent not known %d\n", event[0])); } else if (seq_fetch_mid(scp, event[1], &m) != 0) { ret = 1; SEQ_DEBUG(2, printf("seq_processevent midi unit not found %d\n", event[1])); } else switch (event[0]) { case EV_CHN_VOICE: ret = seq_chnvoice(scp, m, event); break; case EV_CHN_COMMON: ret = seq_chncommon(scp, m, event); break; case EV_SYSEX: ret = seq_sysex(scp, m, event); break; case SEQ_MIDIPUTC: mtx_unlock(&scp->seq_lock); ret = SYNTH_WRITERAW(m, &event[2], 1); mtx_lock(&scp->seq_lock); break; } return ret; } static int seq_addunit(void) { struct seq_softc *scp; int ret; u_char *buf; - gone_in(15, "MIDI sequencer: no longer needed or used"); + gone_in(15, "Warning! MIDI sequencer to be removed soon: no longer " + "needed or used\n"); /* Allocate the softc. */ ret = ENOMEM; scp = malloc(sizeof(*scp), M_DEVBUF, M_NOWAIT | M_ZERO); if (scp == NULL) { SEQ_DEBUG(1, printf("seq_addunit: softc allocation failed.\n")); goto err; } kobj_init((kobj_t)scp, &sequencer_class); buf = malloc(sizeof(*buf) * EV_SZ * 1024, M_TEMP, M_NOWAIT | M_ZERO); if (buf == NULL) goto err; MIDIQ_INIT(scp->in_q, buf, EV_SZ * 1024); buf = malloc(sizeof(*buf) * EV_SZ * 1024, M_TEMP, M_NOWAIT | M_ZERO); if (buf == NULL) goto err; MIDIQ_INIT(scp->out_q, buf, EV_SZ * 1024); ret = EINVAL; scp->midis = malloc(sizeof(kobj_t) * 32, M_TEMP, M_NOWAIT | M_ZERO); scp->midi_flags = malloc(sizeof(*scp->midi_flags) * 32, M_TEMP, M_NOWAIT | M_ZERO); if (scp->midis == NULL || scp->midi_flags == NULL) goto err; scp->flags = 0; mtx_init(&scp->seq_lock, "seqflq", NULL, 0); cv_init(&scp->state_cv, "seqstate"); cv_init(&scp->empty_cv, "seqempty"); cv_init(&scp->reset_cv, "seqtimer"); cv_init(&scp->out_cv, "seqqout"); cv_init(&scp->in_cv, "seqqin"); cv_init(&scp->th_cv, "seqstart"); /* * Init the damn timer */ scp->mapper = midimapper_addseq(scp, &scp->unit, &scp->mapper_cookie); if (scp->mapper == NULL) goto err; scp->seqdev = make_dev(&seq_cdevsw, SND_DEV_SEQ, UID_ROOT, GID_WHEEL, 0666, "sequencer%d", scp->unit); scp->musicdev = make_dev(&seq_cdevsw, SND_DEV_MUSIC, UID_ROOT, GID_WHEEL, 0666, "music%d", scp->unit); if (scp->seqdev == NULL || scp->musicdev == NULL) goto err; /* * TODO: Add to list of sequencers this module provides */ ret = kproc_create (seq_eventthread, scp, NULL, RFHIGHPID, 0, "sequencer %02d", scp->unit); if (ret) goto err; scp->seqdev->si_drv1 = scp->musicdev->si_drv1 = scp; SEQ_DEBUG(2, printf("sequencer %d created scp %p\n", scp->unit, scp)); ret = 0; mtx_lock(&seqinfo_mtx); seqs[nseq++] = scp; mtx_unlock(&seqinfo_mtx); goto ok; err: if (scp != NULL) { if (scp->seqdev != NULL) destroy_dev(scp->seqdev); if (scp->musicdev != NULL) destroy_dev(scp->musicdev); /* * TODO: Destroy mutex and cv */ if (scp->midis != NULL) free(scp->midis, M_TEMP); if (scp->midi_flags != NULL) free(scp->midi_flags, M_TEMP); if (scp->out_q.b) free(scp->out_q.b, M_TEMP); if (scp->in_q.b) free(scp->in_q.b, M_TEMP); free(scp, M_DEVBUF); } ok: return ret; } static int seq_delunit(int unit) { struct seq_softc *scp = seqs[unit]; int i; //SEQ_DEBUG(4, printf("seq_delunit: %d\n", unit)); SEQ_DEBUG(1, printf("seq_delunit: 1 \n")); mtx_lock(&scp->seq_lock); scp->playing = 0; scp->done = 1; cv_broadcast(&scp->out_cv); cv_broadcast(&scp->state_cv); cv_broadcast(&scp->reset_cv); SEQ_DEBUG(1, printf("seq_delunit: 2 \n")); cv_wait(&scp->th_cv, &scp->seq_lock); SEQ_DEBUG(1, printf("seq_delunit: 3.0 \n")); mtx_unlock(&scp->seq_lock); SEQ_DEBUG(1, printf("seq_delunit: 3.1 \n")); cv_destroy(&scp->state_cv); SEQ_DEBUG(1, printf("seq_delunit: 4 \n")); cv_destroy(&scp->empty_cv); SEQ_DEBUG(1, printf("seq_delunit: 5 \n")); cv_destroy(&scp->reset_cv); SEQ_DEBUG(1, printf("seq_delunit: 6 \n")); cv_destroy(&scp->out_cv); SEQ_DEBUG(1, printf("seq_delunit: 7 \n")); cv_destroy(&scp->in_cv); SEQ_DEBUG(1, printf("seq_delunit: 8 \n")); cv_destroy(&scp->th_cv); SEQ_DEBUG(1, printf("seq_delunit: 10 \n")); if (scp->seqdev) destroy_dev(scp->seqdev); SEQ_DEBUG(1, printf("seq_delunit: 11 \n")); if (scp->musicdev) destroy_dev(scp->musicdev); SEQ_DEBUG(1, printf("seq_delunit: 12 \n")); scp->seqdev = scp->musicdev = NULL; if (scp->midis != NULL) free(scp->midis, M_TEMP); SEQ_DEBUG(1, printf("seq_delunit: 13 \n")); if (scp->midi_flags != NULL) free(scp->midi_flags, M_TEMP); SEQ_DEBUG(1, printf("seq_delunit: 14 \n")); free(scp->out_q.b, M_TEMP); SEQ_DEBUG(1, printf("seq_delunit: 15 \n")); free(scp->in_q.b, M_TEMP); SEQ_DEBUG(1, printf("seq_delunit: 16 \n")); mtx_destroy(&scp->seq_lock); SEQ_DEBUG(1, printf("seq_delunit: 17 \n")); free(scp, M_DEVBUF); mtx_lock(&seqinfo_mtx); for (i = unit; i < (nseq - 1); i++) seqs[i] = seqs[i + 1]; nseq--; mtx_unlock(&seqinfo_mtx); return 0; } int seq_modevent(module_t mod, int type, void *data) { int retval, r; retval = 0; switch (type) { case MOD_LOAD: mtx_init(&seqinfo_mtx, "seqmod", NULL, 0); retval = seq_addunit(); break; case MOD_UNLOAD: while (nseq) { r = seq_delunit(nseq - 1); if (r) { retval = r; break; } } if (nseq == 0) { retval = 0; mtx_destroy(&seqinfo_mtx); } break; default: break; } return retval; } static int seq_fetch_mid(struct seq_softc *scp, int unit, kobj_t *md) { if (unit >= scp->midi_number || unit < 0) return EINVAL; *md = scp->midis[unit]; return 0; } int mseq_open(struct cdev *i_dev, int flags, int mode, struct thread *td) { struct seq_softc *scp = i_dev->si_drv1; int i; - gone_in(15, "MIDI sequencer: no longer needed or used"); + gone_in(15, "Warning! MIDI sequencer to be removed soon: no longer " + "needed or used\n"); if (scp == NULL) return ENXIO; SEQ_DEBUG(3, printf("seq_open: scp %p unit %d, flags 0x%x.\n", scp, scp->unit, flags)); /* * Mark this device busy. */ midistat_lock(); mtx_lock(&scp->seq_lock); if (scp->busy) { mtx_unlock(&scp->seq_lock); midistat_unlock(); SEQ_DEBUG(2, printf("seq_open: unit %d is busy.\n", scp->unit)); return EBUSY; } scp->fflags = flags; /* if ((scp->fflags & O_NONBLOCK) != 0) scp->flags |= SEQ_F_NBIO; */ scp->music = MIDIDEV(i_dev) == SND_DEV_MUSIC; /* * Enumerate the available midi devices */ scp->midi_number = 0; scp->maxunits = midimapper_open_locked(scp->mapper, &scp->mapper_cookie); if (scp->maxunits == 0) SEQ_DEBUG(2, printf("seq_open: no midi devices\n")); for (i = 0; i < scp->maxunits; i++) { scp->midis[scp->midi_number] = midimapper_fetch_synth_locked(scp->mapper, scp->mapper_cookie, i); if (scp->midis[scp->midi_number]) { if (SYNTH_OPEN(scp->midis[scp->midi_number], scp, scp->fflags) != 0) scp->midis[scp->midi_number] = NULL; else { scp->midi_flags[scp->midi_number] = SYNTH_QUERY(scp->midis[scp->midi_number]); scp->midi_number++; } } } midistat_unlock(); timer_setvals(scp, 60, 100); timer_start(scp); timer_stop(scp); /* * actually, if we're in rdonly mode, we should start the timer */ /* * TODO: Handle recording now */ scp->out_water = MIDIQ_SIZE(scp->out_q) / 2; scp->busy = 1; mtx_unlock(&scp->seq_lock); SEQ_DEBUG(2, printf("seq_open: opened, mode %s.\n", scp->music ? "music" : "sequencer")); SEQ_DEBUG(2, printf("Sequencer %d %p opened maxunits %d midi_number %d:\n", scp->unit, scp, scp->maxunits, scp->midi_number)); for (i = 0; i < scp->midi_number; i++) SEQ_DEBUG(3, printf(" midi %d %p\n", i, scp->midis[i])); return 0; } /* * mseq_close */ int mseq_close(struct cdev *i_dev, int flags, int mode, struct thread *td) { int i; struct seq_softc *scp = i_dev->si_drv1; int ret; if (scp == NULL) return ENXIO; SEQ_DEBUG(2, printf("seq_close: unit %d.\n", scp->unit)); mtx_lock(&scp->seq_lock); ret = ENXIO; if (scp->busy == 0) goto err; seq_reset(scp); seq_sync(scp); for (i = 0; i < scp->midi_number; i++) if (scp->midis[i]) SYNTH_CLOSE(scp->midis[i]); midimapper_close(scp->mapper, scp->mapper_cookie); timer_stop(scp); scp->busy = 0; ret = 0; err: SEQ_DEBUG(3, printf("seq_close: closed ret = %d.\n", ret)); mtx_unlock(&scp->seq_lock); return ret; } int mseq_read(struct cdev *i_dev, struct uio *uio, int ioflag) { int retval, used; struct seq_softc *scp = i_dev->si_drv1; #define SEQ_RSIZE 32 u_char buf[SEQ_RSIZE]; if (scp == NULL) return ENXIO; SEQ_DEBUG(7, printf("mseq_read: unit %d, resid %zd.\n", scp->unit, uio->uio_resid)); mtx_lock(&scp->seq_lock); if ((scp->fflags & FREAD) == 0) { SEQ_DEBUG(2, printf("mseq_read: unit %d is not for reading.\n", scp->unit)); retval = EIO; goto err1; } /* * Begin recording. */ /* * if ((scp->flags & SEQ_F_READING) == 0) */ /* * TODO, start recording if not alread */ /* * I think the semantics are to return as soon * as possible. * Second thought, it doesn't seem like midimoutain * expects that at all. * TODO: Look up in some sort of spec */ while (uio->uio_resid > 0) { while (MIDIQ_EMPTY(scp->in_q)) { retval = EWOULDBLOCK; /* * I wish I knew which one to care about */ if (scp->fflags & O_NONBLOCK) goto err1; if (ioflag & O_NONBLOCK) goto err1; retval = cv_wait_sig(&scp->in_cv, &scp->seq_lock); if (retval != 0) goto err1; } used = MIN(MIDIQ_LEN(scp->in_q), uio->uio_resid); used = MIN(used, SEQ_RSIZE); SEQ_DEBUG(8, printf("midiread: uiomove cc=%d\n", used)); MIDIQ_DEQ(scp->in_q, buf, used); mtx_unlock(&scp->seq_lock); retval = uiomove(buf, used, uio); mtx_lock(&scp->seq_lock); if (retval) goto err1; } retval = 0; err1: mtx_unlock(&scp->seq_lock); SEQ_DEBUG(6, printf("mseq_read: ret %d, resid %zd.\n", retval, uio->uio_resid)); return retval; } int mseq_write(struct cdev *i_dev, struct uio *uio, int ioflag) { u_char event[EV_SZ], newevent[EV_SZ], ev_code; struct seq_softc *scp = i_dev->si_drv1; int retval; int used; SEQ_DEBUG(7, printf("seq_write: unit %d, resid %zd.\n", scp->unit, uio->uio_resid)); if (scp == NULL) return ENXIO; mtx_lock(&scp->seq_lock); if ((scp->fflags & FWRITE) == 0) { SEQ_DEBUG(2, printf("seq_write: unit %d is not for writing.\n", scp->unit)); retval = EIO; goto err0; } while (uio->uio_resid > 0) { while (MIDIQ_AVAIL(scp->out_q) == 0) { retval = EWOULDBLOCK; if (scp->fflags & O_NONBLOCK) goto err0; if (ioflag & O_NONBLOCK) goto err0; SEQ_DEBUG(8, printf("seq_write cvwait\n")); scp->playing = 1; cv_broadcast(&scp->out_cv); cv_broadcast(&scp->state_cv); retval = cv_wait_sig(&scp->out_cv, &scp->seq_lock); /* * We slept, maybe things have changed since last * dying check */ if (retval != 0) goto err0; #if 0 /* * Useless test */ if (scp != i_dev->si_drv1) retval = ENXIO; #endif } used = MIN(uio->uio_resid, 4); SEQ_DEBUG(8, printf("seqout: resid %zd len %jd avail %jd\n", uio->uio_resid, (intmax_t)MIDIQ_LEN(scp->out_q), (intmax_t)MIDIQ_AVAIL(scp->out_q))); if (used != 4) { retval = ENXIO; goto err0; } mtx_unlock(&scp->seq_lock); retval = uiomove(event, used, uio); mtx_lock(&scp->seq_lock); if (retval) goto err0; ev_code = event[0]; SEQ_DEBUG(8, printf("seq_write: unit %d, event %s.\n", scp->unit, midi_cmdname(ev_code, cmdtab_seqevent))); /* Have a look at the event code. */ if (ev_code == SEQ_FULLSIZE) { /* * TODO: restore code for SEQ_FULLSIZE */ #if 0 /* * A long event, these are the patches/samples for a * synthesizer. */ midiunit = *(u_short *)&event[2]; mtx_lock(&sd->seq_lock); ret = lookup_mididev(scp, midiunit, LOOKUP_OPEN, &md); mtx_unlock(&sd->seq_lock); if (ret != 0) return (ret); SEQ_DEBUG(printf("seq_write: loading a patch to the unit %d.\n", midiunit)); ret = md->synth.loadpatch(md, *(short *)&event[0], buf, p + 4, count, 0); return (ret); #else /* * For now, just flush the darn buffer */ SEQ_DEBUG(2, printf("seq_write: SEQ_FULLSIZE flusing buffer.\n")); while (uio->uio_resid > 0) { mtx_unlock(&scp->seq_lock); retval = uiomove(event, MIN(EV_SZ, uio->uio_resid), uio); mtx_lock(&scp->seq_lock); if (retval) goto err0; } retval = 0; goto err0; #endif } retval = EINVAL; if (ev_code >= 128) { int error; /* * Some sort of an extended event. The size is eight * bytes. scoop extra info. */ if (scp->music && ev_code == SEQ_EXTENDED) { SEQ_DEBUG(2, printf("seq_write: invalid level two event %x.\n", ev_code)); goto err0; } mtx_unlock(&scp->seq_lock); if (uio->uio_resid < 4) error = EINVAL; else error = uiomove((caddr_t)&event[4], 4, uio); mtx_lock(&scp->seq_lock); if (error) { SEQ_DEBUG(2, printf("seq_write: user memory mangled?\n")); goto err0; } } else { /* * Size four event. */ if (scp->music) { SEQ_DEBUG(2, printf("seq_write: four byte event in music mode.\n")); goto err0; } } if (ev_code == SEQ_MIDIPUTC) { /* * TODO: event[2] is unit number to receive char. * Range check it. */ } if (scp->music) { #ifdef not_ever_ever if (event[0] == EV_TIMING && (event[1] == TMR_START || event[1] == TMR_STOP)) { /* * For now, try to make midimoutain work by * forcing these events to be processed * immediately. */ seq_processevent(scp, event); } else MIDIQ_ENQ(scp->out_q, event, EV_SZ); #else MIDIQ_ENQ(scp->out_q, event, EV_SZ); #endif } else { if (seq_convertold(event, newevent) > 0) MIDIQ_ENQ(scp->out_q, newevent, EV_SZ); #if 0 else goto err0; #endif } } scp->playing = 1; cv_broadcast(&scp->state_cv); cv_broadcast(&scp->out_cv); retval = 0; err0: SEQ_DEBUG(6, printf("seq_write done: leftover buffer length %zd retval %d\n", uio->uio_resid, retval)); mtx_unlock(&scp->seq_lock); return retval; } int mseq_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) { int midiunit, ret, tmp; struct seq_softc *scp = i_dev->si_drv1; struct synth_info *synthinfo; struct midi_info *midiinfo; u_char event[EV_SZ]; u_char newevent[EV_SZ]; kobj_t md; /* * struct snd_size *sndsize; */ if (scp == NULL) return ENXIO; SEQ_DEBUG(6, printf("seq_ioctl: unit %d, cmd %s.\n", scp->unit, midi_cmdname(cmd, cmdtab_seqioctl))); ret = 0; switch (cmd) { case SNDCTL_SEQ_GETTIME: /* * ioctl needed by libtse */ mtx_lock(&scp->seq_lock); *(int *)arg = timer_now(scp); mtx_unlock(&scp->seq_lock); SEQ_DEBUG(6, printf("seq_ioctl: gettime %d.\n", *(int *)arg)); ret = 0; break; case SNDCTL_TMR_METRONOME: /* fallthrough */ case SNDCTL_TMR_SOURCE: /* * Not implemented */ ret = 0; break; case SNDCTL_TMR_TEMPO: event[1] = TMR_TEMPO; event[4] = *(int *)arg & 0xFF; event[5] = (*(int *)arg >> 8) & 0xFF; event[6] = (*(int *)arg >> 16) & 0xFF; event[7] = (*(int *)arg >> 24) & 0xFF; goto timerevent; case SNDCTL_TMR_TIMEBASE: event[1] = TMR_TIMERBASE; event[4] = *(int *)arg & 0xFF; event[5] = (*(int *)arg >> 8) & 0xFF; event[6] = (*(int *)arg >> 16) & 0xFF; event[7] = (*(int *)arg >> 24) & 0xFF; goto timerevent; case SNDCTL_TMR_START: event[1] = TMR_START; goto timerevent; case SNDCTL_TMR_STOP: event[1] = TMR_STOP; goto timerevent; case SNDCTL_TMR_CONTINUE: event[1] = TMR_CONTINUE; timerevent: event[0] = EV_TIMING; mtx_lock(&scp->seq_lock); if (!scp->music) { ret = EINVAL; mtx_unlock(&scp->seq_lock); break; } seq_processevent(scp, event); mtx_unlock(&scp->seq_lock); break; case SNDCTL_TMR_SELECT: SEQ_DEBUG(2, printf("seq_ioctl: SNDCTL_TMR_SELECT not supported\n")); ret = EINVAL; break; case SNDCTL_SEQ_SYNC: if (mode == O_RDONLY) { ret = 0; break; } mtx_lock(&scp->seq_lock); ret = seq_sync(scp); mtx_unlock(&scp->seq_lock); break; case SNDCTL_SEQ_PANIC: /* fallthrough */ case SNDCTL_SEQ_RESET: /* * SNDCTL_SEQ_PANIC == SNDCTL_SEQ_RESET */ mtx_lock(&scp->seq_lock); seq_reset(scp); mtx_unlock(&scp->seq_lock); ret = 0; break; case SNDCTL_SEQ_TESTMIDI: mtx_lock(&scp->seq_lock); /* * TODO: SNDCTL_SEQ_TESTMIDI now means "can I write to the * device?". */ mtx_unlock(&scp->seq_lock); break; #if 0 case SNDCTL_SEQ_GETINCOUNT: if (mode == O_WRONLY) *(int *)arg = 0; else { mtx_lock(&scp->seq_lock); *(int *)arg = scp->in_q.rl; mtx_unlock(&scp->seq_lock); SEQ_DEBUG(printf("seq_ioctl: incount %d.\n", *(int *)arg)); } ret = 0; break; case SNDCTL_SEQ_GETOUTCOUNT: if (mode == O_RDONLY) *(int *)arg = 0; else { mtx_lock(&scp->seq_lock); *(int *)arg = scp->out_q.fl; mtx_unlock(&scp->seq_lock); SEQ_DEBUG(printf("seq_ioctl: outcount %d.\n", *(int *)arg)); } ret = 0; break; #endif case SNDCTL_SEQ_CTRLRATE: if (*(int *)arg != 0) { ret = EINVAL; break; } mtx_lock(&scp->seq_lock); *(int *)arg = scp->timerbase; mtx_unlock(&scp->seq_lock); SEQ_DEBUG(3, printf("seq_ioctl: ctrlrate %d.\n", *(int *)arg)); ret = 0; break; /* * TODO: ioctl SNDCTL_SEQ_RESETSAMPLES */ #if 0 case SNDCTL_SEQ_RESETSAMPLES: mtx_lock(&scp->seq_lock); ret = lookup_mididev(scp, *(int *)arg, LOOKUP_OPEN, &md); mtx_unlock(&scp->seq_lock); if (ret != 0) break; ret = midi_ioctl(MIDIMKDEV(major(i_dev), *(int *)arg, SND_DEV_MIDIN), cmd, arg, mode, td); break; #endif case SNDCTL_SEQ_NRSYNTHS: mtx_lock(&scp->seq_lock); *(int *)arg = scp->midi_number; mtx_unlock(&scp->seq_lock); SEQ_DEBUG(3, printf("seq_ioctl: synths %d.\n", *(int *)arg)); ret = 0; break; case SNDCTL_SEQ_NRMIDIS: mtx_lock(&scp->seq_lock); if (scp->music) *(int *)arg = 0; else { /* * TODO: count the numbder of devices that can WRITERAW */ *(int *)arg = scp->midi_number; } mtx_unlock(&scp->seq_lock); SEQ_DEBUG(3, printf("seq_ioctl: midis %d.\n", *(int *)arg)); ret = 0; break; /* * TODO: ioctl SNDCTL_SYNTH_MEMAVL */ #if 0 case SNDCTL_SYNTH_MEMAVL: mtx_lock(&scp->seq_lock); ret = lookup_mididev(scp, *(int *)arg, LOOKUP_OPEN, &md); mtx_unlock(&scp->seq_lock); if (ret != 0) break; ret = midi_ioctl(MIDIMKDEV(major(i_dev), *(int *)arg, SND_DEV_MIDIN), cmd, arg, mode, td); break; #endif case SNDCTL_SEQ_OUTOFBAND: for (ret = 0; ret < EV_SZ; ret++) event[ret] = (u_char)arg[0]; mtx_lock(&scp->seq_lock); if (scp->music) ret = seq_processevent(scp, event); else { if (seq_convertold(event, newevent) > 0) ret = seq_processevent(scp, newevent); else ret = EINVAL; } mtx_unlock(&scp->seq_lock); break; case SNDCTL_SYNTH_INFO: synthinfo = (struct synth_info *)arg; midiunit = synthinfo->device; mtx_lock(&scp->seq_lock); if (seq_fetch_mid(scp, midiunit, &md) == 0) { bzero(synthinfo, sizeof(*synthinfo)); synthinfo->name[0] = 'f'; synthinfo->name[1] = 'a'; synthinfo->name[2] = 'k'; synthinfo->name[3] = 'e'; synthinfo->name[4] = 's'; synthinfo->name[5] = 'y'; synthinfo->name[6] = 'n'; synthinfo->name[7] = 't'; synthinfo->name[8] = 'h'; synthinfo->device = midiunit; synthinfo->synth_type = SYNTH_TYPE_MIDI; synthinfo->capabilities = scp->midi_flags[midiunit]; ret = 0; } else ret = EINVAL; mtx_unlock(&scp->seq_lock); break; case SNDCTL_MIDI_INFO: midiinfo = (struct midi_info *)arg; midiunit = midiinfo->device; mtx_lock(&scp->seq_lock); if (seq_fetch_mid(scp, midiunit, &md) == 0) { bzero(midiinfo, sizeof(*midiinfo)); midiinfo->name[0] = 'f'; midiinfo->name[1] = 'a'; midiinfo->name[2] = 'k'; midiinfo->name[3] = 'e'; midiinfo->name[4] = 'm'; midiinfo->name[5] = 'i'; midiinfo->name[6] = 'd'; midiinfo->name[7] = 'i'; midiinfo->device = midiunit; midiinfo->capabilities = scp->midi_flags[midiunit]; /* * TODO: What devtype? */ midiinfo->dev_type = 0x01; ret = 0; } else ret = EINVAL; mtx_unlock(&scp->seq_lock); break; case SNDCTL_SEQ_THRESHOLD: mtx_lock(&scp->seq_lock); RANGE(*(int *)arg, 1, MIDIQ_SIZE(scp->out_q) - 1); scp->out_water = *(int *)arg; mtx_unlock(&scp->seq_lock); SEQ_DEBUG(3, printf("seq_ioctl: water %d.\n", *(int *)arg)); ret = 0; break; case SNDCTL_MIDI_PRETIME: tmp = *(int *)arg; if (tmp < 0) tmp = 0; mtx_lock(&scp->seq_lock); scp->pre_event_timeout = (hz * tmp) / 10; *(int *)arg = scp->pre_event_timeout; mtx_unlock(&scp->seq_lock); SEQ_DEBUG(3, printf("seq_ioctl: pretime %d.\n", *(int *)arg)); ret = 0; break; case SNDCTL_FM_4OP_ENABLE: case SNDCTL_PMGR_IFACE: case SNDCTL_PMGR_ACCESS: /* * Patch manager and fm are ded, ded, ded. */ /* fallthrough */ default: /* * TODO: Consider ioctl default case. * Old code used to * if ((scp->fflags & O_ACCMODE) == FREAD) { * ret = EIO; * break; * } * Then pass on the ioctl to device 0 */ SEQ_DEBUG(2, printf("seq_ioctl: unsupported IOCTL %ld.\n", cmd)); ret = EINVAL; break; } return ret; } int mseq_poll(struct cdev *i_dev, int events, struct thread *td) { int ret, lim; struct seq_softc *scp = i_dev->si_drv1; SEQ_DEBUG(3, printf("seq_poll: unit %d.\n", scp->unit)); SEQ_DEBUG(1, printf("seq_poll: unit %d.\n", scp->unit)); mtx_lock(&scp->seq_lock); ret = 0; /* Look up the appropriate queue and select it. */ if ((events & (POLLOUT | POLLWRNORM)) != 0) { /* Start playing. */ scp->playing = 1; cv_broadcast(&scp->state_cv); cv_broadcast(&scp->out_cv); lim = scp->out_water; if (MIDIQ_AVAIL(scp->out_q) < lim) /* No enough space, record select. */ selrecord(td, &scp->out_sel); else /* We can write now. */ ret |= events & (POLLOUT | POLLWRNORM); } if ((events & (POLLIN | POLLRDNORM)) != 0) { /* TODO: Start recording. */ /* Find out the boundary. */ lim = 1; if (MIDIQ_LEN(scp->in_q) < lim) /* No data ready, record select. */ selrecord(td, &scp->in_sel); else /* We can read now. */ ret |= events & (POLLIN | POLLRDNORM); } mtx_unlock(&scp->seq_lock); return (ret); } #if 0 static void sein_qtr(void *p, void /* mididev_info */ *md) { struct seq_softc *scp; scp = (struct seq_softc *)p; mtx_lock(&scp->seq_lock); /* Restart playing if we have the data to output. */ if (scp->queueout_pending) seq_callback(scp, SEQ_CB_START | SEQ_CB_WR); /* Check the midi device if we are reading. */ if ((scp->flags & SEQ_F_READING) != 0) seq_midiinput(scp, md); mtx_unlock(&scp->seq_lock); } #endif /* * seq_convertold * Was the old playevent. Use this to convert and old * style /dev/sequencer event to a /dev/music event */ static int seq_convertold(u_char *event, u_char *out) { int used; u_char dev, chn, note, vel; out[0] = out[1] = out[2] = out[3] = out[4] = out[5] = out[6] = out[7] = 0; dev = 0; chn = event[1]; note = event[2]; vel = event[3]; used = 0; restart: /* * TODO: Debug statement */ switch (event[0]) { case EV_TIMING: case EV_CHN_VOICE: case EV_CHN_COMMON: case EV_SYSEX: case EV_SEQ_LOCAL: out[0] = event[0]; out[1] = event[1]; out[2] = event[2]; out[3] = event[3]; out[4] = event[4]; out[5] = event[5]; out[6] = event[6]; out[7] = event[7]; used += 8; break; case SEQ_NOTEOFF: out[0] = EV_CHN_VOICE; out[1] = dev; out[2] = MIDI_NOTEOFF; out[3] = chn; out[4] = note; out[5] = 255; used += 4; break; case SEQ_NOTEON: out[0] = EV_CHN_VOICE; out[1] = dev; out[2] = MIDI_NOTEON; out[3] = chn; out[4] = note; out[5] = vel; used += 4; break; /* * wait delay = (event[2] << 16) + (event[3] << 8) + event[4] */ case SEQ_PGMCHANGE: out[0] = EV_CHN_COMMON; out[1] = dev; out[2] = MIDI_PGM_CHANGE; out[3] = chn; out[4] = note; out[5] = vel; used += 4; break; /* out[0] = EV_TIMING; out[1] = dev; out[2] = MIDI_PGM_CHANGE; out[3] = chn; out[4] = note; out[5] = vel; SEQ_DEBUG(4,printf("seq_playevent: synctimer\n")); break; */ case SEQ_MIDIPUTC: SEQ_DEBUG(4, printf("seq_playevent: put data 0x%02x, unit %d.\n", event[1], event[2])); /* * Pass through to the midi device. * device = event[2] * data = event[1] */ out[0] = SEQ_MIDIPUTC; out[1] = dev; out[2] = chn; used += 4; break; #ifdef notyet case SEQ_ECHO: /* * This isn't handled here yet because I don't know if I can * just use four bytes events. There might be consequences * in the _read routing */ if (seq_copytoinput(scp, event, 4) == EAGAIN) { ret = QUEUEFULL; break; } ret = MORE; break; #endif case SEQ_EXTENDED: switch (event[1]) { case SEQ_NOTEOFF: case SEQ_NOTEON: case SEQ_PGMCHANGE: event++; used = 4; goto restart; break; case SEQ_AFTERTOUCH: /* * SYNTH_AFTERTOUCH(md, event[3], event[4]) */ case SEQ_BALANCE: /* * SYNTH_PANNING(md, event[3], (char)event[4]) */ case SEQ_CONTROLLER: /* * SYNTH_CONTROLLER(md, event[3], event[4], *(short *)&event[5]) */ case SEQ_VOLMODE: /* * SYNTH_VOLUMEMETHOD(md, event[3]) */ default: SEQ_DEBUG(2, printf("seq_convertold: SEQ_EXTENDED type %d" "not handled\n", event[1])); break; } break; case SEQ_WAIT: out[0] = EV_TIMING; out[1] = TMR_WAIT_REL; out[4] = event[2]; out[5] = event[3]; out[6] = event[4]; SEQ_DEBUG(5, printf("SEQ_WAIT %d", event[2] + (event[3] << 8) + (event[4] << 24))); used += 4; break; case SEQ_ECHO: case SEQ_SYNCTIMER: case SEQ_PRIVATE: default: SEQ_DEBUG(2, printf("seq_convertold: event type %d not handled %d %d %d\n", event[0], event[1], event[2], event[3])); break; } return used; } /* * Writting to the sequencer buffer never blocks and drops * input which cannot be queued */ void seq_copytoinput(struct seq_softc *scp, u_char *event, int len) { mtx_assert(&scp->seq_lock, MA_OWNED); if (MIDIQ_AVAIL(scp->in_q) < len) { /* * ENOROOM? EINPUTDROPPED? ETOUGHLUCK? */ SEQ_DEBUG(2, printf("seq_copytoinput: queue full\n")); } else { MIDIQ_ENQ(scp->in_q, event, len); selwakeup(&scp->in_sel); cv_broadcast(&scp->in_cv); } } static int seq_chnvoice(struct seq_softc *scp, kobj_t md, u_char *event) { int ret, voice; u_char cmd, chn, note, parm; ret = 0; cmd = event[2]; chn = event[3]; note = event[4]; parm = event[5]; mtx_assert(&scp->seq_lock, MA_OWNED); SEQ_DEBUG(5, printf("seq_chnvoice: unit %d, dev %d, cmd %s," " chn %d, note %d, parm %d.\n", scp->unit, event[1], midi_cmdname(cmd, cmdtab_seqcv), chn, note, parm)); voice = SYNTH_ALLOC(md, chn, note); mtx_unlock(&scp->seq_lock); switch (cmd) { case MIDI_NOTEON: if (note < 128 || note == 255) { #if 0 if (scp->music && chn == 9) { /* * This channel is a percussion. The note * number is the patch number. */ /* mtx_unlock(&scp->seq_lock); if (SYNTH_SETINSTR(md, voice, 128 + note) == EAGAIN) { mtx_lock(&scp->seq_lock); return (QUEUEFULL); } mtx_lock(&scp->seq_lock); */ note = 60; /* Middle C. */ } #endif if (scp->music) { /* mtx_unlock(&scp->seq_lock); if (SYNTH_SETUPVOICE(md, voice, chn) == EAGAIN) { mtx_lock(&scp->seq_lock); return (QUEUEFULL); } mtx_lock(&scp->seq_lock); */ } SYNTH_STARTNOTE(md, voice, note, parm); } break; case MIDI_NOTEOFF: SYNTH_KILLNOTE(md, voice, note, parm); break; case MIDI_KEY_PRESSURE: SYNTH_AFTERTOUCH(md, voice, parm); break; default: ret = 1; SEQ_DEBUG(2, printf("seq_chnvoice event type %d not handled\n", event[1])); break; } mtx_lock(&scp->seq_lock); return ret; } static int seq_chncommon(struct seq_softc *scp, kobj_t md, u_char *event) { int ret; u_short w14; u_char cmd, chn, p1; ret = 0; cmd = event[2]; chn = event[3]; p1 = event[4]; w14 = *(u_short *)&event[6]; SEQ_DEBUG(5, printf("seq_chncommon: unit %d, dev %d, cmd %s, chn %d," " p1 %d, w14 %d.\n", scp->unit, event[1], midi_cmdname(cmd, cmdtab_seqccmn), chn, p1, w14)); mtx_unlock(&scp->seq_lock); switch (cmd) { case MIDI_PGM_CHANGE: SEQ_DEBUG(4, printf("seq_chncommon pgmchn chn %d pg %d\n", chn, p1)); SYNTH_SETINSTR(md, chn, p1); break; case MIDI_CTL_CHANGE: SEQ_DEBUG(4, printf("seq_chncommon ctlch chn %d pg %d %d\n", chn, p1, w14)); SYNTH_CONTROLLER(md, chn, p1, w14); break; case MIDI_PITCH_BEND: if (scp->music) { /* * TODO: MIDI_PITCH_BEND */ #if 0 mtx_lock(&md->synth.vc_mtx); md->synth.chn_info[chn].bender_value = w14; if (md->midiunit >= 0) { /* * Handle all of the notes playing on this * channel. */ key = ((int)chn << 8); for (i = 0; i < md->synth.alloc.max_voice; i++) if ((md->synth.alloc.map[i] & 0xff00) == key) { mtx_unlock(&md->synth.vc_mtx); mtx_unlock(&scp->seq_lock); if (md->synth.bender(md, i, w14) == EAGAIN) { mtx_lock(&scp->seq_lock); return (QUEUEFULL); } mtx_lock(&scp->seq_lock); } } else { mtx_unlock(&md->synth.vc_mtx); mtx_unlock(&scp->seq_lock); if (md->synth.bender(md, chn, w14) == EAGAIN) { mtx_lock(&scp->seq_lock); return (QUEUEFULL); } mtx_lock(&scp->seq_lock); } #endif } else SYNTH_BENDER(md, chn, w14); break; default: ret = 1; SEQ_DEBUG(2, printf("seq_chncommon event type %d not handled.\n", event[1])); break; } mtx_lock(&scp->seq_lock); return ret; } static int seq_timing(struct seq_softc *scp, u_char *event) { int param; int ret; ret = 0; param = event[4] + (event[5] << 8) + (event[6] << 16) + (event[7] << 24); SEQ_DEBUG(5, printf("seq_timing: unit %d, cmd %d, param %d.\n", scp->unit, event[1], param)); switch (event[1]) { case TMR_WAIT_REL: timer_wait(scp, param, 0); break; case TMR_WAIT_ABS: timer_wait(scp, param, 1); break; case TMR_START: timer_start(scp); cv_broadcast(&scp->reset_cv); break; case TMR_STOP: timer_stop(scp); /* * The following cv_broadcast isn't needed since we only * wait for 0->1 transitions. It probably won't hurt */ cv_broadcast(&scp->reset_cv); break; case TMR_CONTINUE: timer_continue(scp); cv_broadcast(&scp->reset_cv); break; case TMR_TEMPO: if (param < 8) param = 8; if (param > 360) param = 360; SEQ_DEBUG(4, printf("Timer set tempo %d\n", param)); timer_setvals(scp, param, scp->timerbase); break; case TMR_TIMERBASE: if (param < 1) param = 1; if (param > 1000) param = 1000; SEQ_DEBUG(4, printf("Timer set timerbase %d\n", param)); timer_setvals(scp, scp->tempo, param); break; case TMR_ECHO: /* * TODO: Consider making 4-byte events for /dev/sequencer * PRO: Maybe needed by legacy apps * CON: soundcard.h has been warning for a while many years * to expect 8 byte events. */ #if 0 if (scp->music) seq_copytoinput(scp, event, 8); else { param = (param << 8 | SEQ_ECHO); seq_copytoinput(scp, (u_char *)¶m, 4); } #else seq_copytoinput(scp, event, 8); #endif break; default: SEQ_DEBUG(2, printf("seq_timing event type %d not handled.\n", event[1])); ret = 1; break; } return ret; } static int seq_local(struct seq_softc *scp, u_char *event) { int ret; ret = 0; mtx_assert(&scp->seq_lock, MA_OWNED); SEQ_DEBUG(5, printf("seq_local: unit %d, cmd %d\n", scp->unit, event[1])); switch (event[1]) { default: SEQ_DEBUG(1, printf("seq_local event type %d not handled\n", event[1])); ret = 1; break; } return ret; } static int seq_sysex(struct seq_softc *scp, kobj_t md, u_char *event) { int i, l; mtx_assert(&scp->seq_lock, MA_OWNED); SEQ_DEBUG(5, printf("seq_sysex: unit %d device %d\n", scp->unit, event[1])); l = 0; for (i = 0; i < 6 && event[i + 2] != 0xff; i++) l = i + 1; if (l > 0) { mtx_unlock(&scp->seq_lock); if (SYNTH_SENDSYSEX(md, &event[2], l) == EAGAIN) { mtx_lock(&scp->seq_lock); return 1; } mtx_lock(&scp->seq_lock); } return 0; } /* * Reset no longer closes the raw devices nor seq_sync's * Callers are IOCTL and seq_close */ static void seq_reset(struct seq_softc *scp) { int chn, i; kobj_t m; mtx_assert(&scp->seq_lock, MA_OWNED); SEQ_DEBUG(5, printf("seq_reset: unit %d.\n", scp->unit)); /* * Stop reading and writing. */ /* scp->recording = 0; */ scp->playing = 0; cv_broadcast(&scp->state_cv); cv_broadcast(&scp->out_cv); cv_broadcast(&scp->reset_cv); /* * For now, don't reset the timers. */ MIDIQ_CLEAR(scp->in_q); MIDIQ_CLEAR(scp->out_q); for (i = 0; i < scp->midi_number; i++) { m = scp->midis[i]; mtx_unlock(&scp->seq_lock); SYNTH_RESET(m); for (chn = 0; chn < 16; chn++) { SYNTH_CONTROLLER(m, chn, 123, 0); SYNTH_CONTROLLER(m, chn, 121, 0); SYNTH_BENDER(m, chn, 1 << 13); } mtx_lock(&scp->seq_lock); } } /* * seq_sync * *really* flush the output queue * flush the event queue, then flush the synthsisers. * Callers are IOCTL and close */ #define SEQ_SYNC_TIMEOUT 8 static int seq_sync(struct seq_softc *scp) { int i, rl, sync[16], done; mtx_assert(&scp->seq_lock, MA_OWNED); SEQ_DEBUG(4, printf("seq_sync: unit %d.\n", scp->unit)); /* * Wait until output queue is empty. Check every so often to see if * the queue is moving along. If it isn't just abort. */ while (!MIDIQ_EMPTY(scp->out_q)) { if (!scp->playing) { scp->playing = 1; cv_broadcast(&scp->state_cv); cv_broadcast(&scp->out_cv); } rl = MIDIQ_LEN(scp->out_q); i = cv_timedwait_sig(&scp->out_cv, &scp->seq_lock, SEQ_SYNC_TIMEOUT * hz); if (i == EINTR || i == ERESTART) { if (i == EINTR) { /* * XXX: I don't know why we stop playing */ scp->playing = 0; cv_broadcast(&scp->out_cv); } return i; } if (i == EWOULDBLOCK && rl == MIDIQ_LEN(scp->out_q) && scp->waiting == 0) { /* * A queue seems to be stuck up. Give up and clear * queues. */ MIDIQ_CLEAR(scp->out_q); scp->playing = 0; cv_broadcast(&scp->state_cv); cv_broadcast(&scp->out_cv); cv_broadcast(&scp->reset_cv); /* * TODO: Consider if the raw devices need to be flushed */ SEQ_DEBUG(1, printf("seq_sync queue stuck, aborting\n")); return i; } } scp->playing = 0; /* * Since syncing a midi device might block, unlock scp->seq_lock. */ mtx_unlock(&scp->seq_lock); for (i = 0; i < scp->midi_number; i++) sync[i] = 1; do { done = 1; for (i = 0; i < scp->midi_number; i++) if (sync[i]) { if (SYNTH_INSYNC(scp->midis[i]) == 0) sync[i] = 0; else done = 0; } if (!done) DELAY(5000); } while (!done); mtx_lock(&scp->seq_lock); return 0; } char * midi_cmdname(int cmd, midi_cmdtab *tab) { while (tab->name != NULL) { if (cmd == tab->cmd) return (tab->name); tab++; } return ("unknown"); } diff --git a/sys/kern/subr_bus.c b/sys/kern/subr_bus.c index 4de1df0c248f..e7e8896fc100 100644 --- a/sys/kern/subr_bus.c +++ b/sys/kern/subr_bus.c @@ -1,6327 +1,6333 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1997,1998,2003 Doug Rabson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include "opt_bus.h" #include "opt_ddb.h" #include "opt_iommu.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INTRNG #include #endif #include #include #include #include #include #include SYSCTL_NODE(_hw, OID_AUTO, bus, CTLFLAG_RW | CTLFLAG_MPSAFE, NULL, NULL); SYSCTL_ROOT_NODE(OID_AUTO, dev, CTLFLAG_RW | CTLFLAG_MPSAFE, NULL, NULL); static bool disable_failed_devs = false; SYSCTL_BOOL(_hw_bus, OID_AUTO, disable_failed_devices, CTLFLAG_RWTUN, &disable_failed_devs, 0, "Do not retry attaching devices that return an error from DEVICE_ATTACH the first time"); /* * Used to attach drivers to devclasses. */ typedef struct driverlink *driverlink_t; struct driverlink { kobj_class_t driver; TAILQ_ENTRY(driverlink) link; /* list of drivers in devclass */ int pass; int flags; #define DL_DEFERRED_PROBE 1 /* Probe deferred on this */ TAILQ_ENTRY(driverlink) passlink; }; /* * Forward declarations */ typedef TAILQ_HEAD(devclass_list, devclass) devclass_list_t; typedef TAILQ_HEAD(driver_list, driverlink) driver_list_t; typedef TAILQ_HEAD(device_list, _device) device_list_t; struct devclass { TAILQ_ENTRY(devclass) link; devclass_t parent; /* parent in devclass hierarchy */ driver_list_t drivers; /* bus devclasses store drivers for bus */ char *name; device_t *devices; /* array of devices indexed by unit */ int maxunit; /* size of devices array */ int flags; #define DC_HAS_CHILDREN 1 struct sysctl_ctx_list sysctl_ctx; struct sysctl_oid *sysctl_tree; }; struct device_prop_elm { const char *name; void *val; void *dtr_ctx; device_prop_dtr_t dtr; LIST_ENTRY(device_prop_elm) link; }; TASKQUEUE_DEFINE_THREAD(bus); static void device_destroy_props(device_t dev); /** * @brief Implementation of _device. * * The structure is named "_device" instead of "device" to avoid type confusion * caused by other subsystems defining a (struct device). */ struct _device { /* * A device is a kernel object. The first field must be the * current ops table for the object. */ KOBJ_FIELDS; /* * Device hierarchy. */ TAILQ_ENTRY(_device) link; /**< list of devices in parent */ TAILQ_ENTRY(_device) devlink; /**< global device list membership */ device_t parent; /**< parent of this device */ device_list_t children; /**< list of child devices */ /* * Details of this device. */ driver_t *driver; /**< current driver */ devclass_t devclass; /**< current device class */ int unit; /**< current unit number */ char* nameunit; /**< name+unit e.g. foodev0 */ char* desc; /**< driver specific description */ u_int busy; /**< count of calls to device_busy() */ device_state_t state; /**< current device state */ uint32_t devflags; /**< api level flags for device_get_flags() */ u_int flags; /**< internal device flags */ u_int order; /**< order from device_add_child_ordered() */ void *ivars; /**< instance variables */ void *softc; /**< current driver's variables */ LIST_HEAD(, device_prop_elm) props; struct sysctl_ctx_list sysctl_ctx; /**< state for sysctl variables */ struct sysctl_oid *sysctl_tree; /**< state for sysctl variables */ }; static MALLOC_DEFINE(M_BUS, "bus", "Bus data structures"); static MALLOC_DEFINE(M_BUS_SC, "bus-sc", "Bus data structures, softc"); EVENTHANDLER_LIST_DEFINE(device_attach); EVENTHANDLER_LIST_DEFINE(device_detach); EVENTHANDLER_LIST_DEFINE(device_nomatch); EVENTHANDLER_LIST_DEFINE(dev_lookup); static void devctl2_init(void); static bool device_frozen; #define DRIVERNAME(d) ((d)? d->name : "no driver") #define DEVCLANAME(d) ((d)? d->name : "no devclass") #ifdef BUS_DEBUG static int bus_debug = 1; SYSCTL_INT(_debug, OID_AUTO, bus_debug, CTLFLAG_RWTUN, &bus_debug, 0, "Bus debug level"); #define PDEBUG(a) if (bus_debug) {printf("%s:%d: ", __func__, __LINE__), printf a; printf("\n");} #define DEVICENAME(d) ((d)? device_get_name(d): "no device") /** * Produce the indenting, indent*2 spaces plus a '.' ahead of that to * prevent syslog from deleting initial spaces */ #define indentprintf(p) do { int iJ; printf("."); for (iJ=0; iJparent ? dc->parent->name : ""; break; default: return (EINVAL); } return (SYSCTL_OUT_STR(req, value)); } static void devclass_sysctl_init(devclass_t dc) { if (dc->sysctl_tree != NULL) return; sysctl_ctx_init(&dc->sysctl_ctx); dc->sysctl_tree = SYSCTL_ADD_NODE(&dc->sysctl_ctx, SYSCTL_STATIC_CHILDREN(_dev), OID_AUTO, dc->name, CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, ""); SYSCTL_ADD_PROC(&dc->sysctl_ctx, SYSCTL_CHILDREN(dc->sysctl_tree), OID_AUTO, "%parent", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dc, DEVCLASS_SYSCTL_PARENT, devclass_sysctl_handler, "A", "parent class"); } enum { DEVICE_SYSCTL_DESC, DEVICE_SYSCTL_DRIVER, DEVICE_SYSCTL_LOCATION, DEVICE_SYSCTL_PNPINFO, DEVICE_SYSCTL_PARENT, DEVICE_SYSCTL_IOMMU, }; static int device_sysctl_handler(SYSCTL_HANDLER_ARGS) { struct sbuf sb; device_t dev = (device_t)arg1; device_t iommu; int error; uint16_t rid; const char *c; sbuf_new_for_sysctl(&sb, NULL, 1024, req); sbuf_clear_flags(&sb, SBUF_INCLUDENUL); bus_topo_lock(); switch (arg2) { case DEVICE_SYSCTL_DESC: sbuf_cat(&sb, dev->desc ? dev->desc : ""); break; case DEVICE_SYSCTL_DRIVER: sbuf_cat(&sb, dev->driver ? dev->driver->name : ""); break; case DEVICE_SYSCTL_LOCATION: bus_child_location(dev, &sb); break; case DEVICE_SYSCTL_PNPINFO: bus_child_pnpinfo(dev, &sb); break; case DEVICE_SYSCTL_PARENT: sbuf_cat(&sb, dev->parent ? dev->parent->nameunit : ""); break; case DEVICE_SYSCTL_IOMMU: iommu = NULL; error = device_get_prop(dev, DEV_PROP_NAME_IOMMU, (void **)&iommu); c = ""; if (error == 0 && iommu != NULL) { sbuf_printf(&sb, "unit=%s", device_get_nameunit(iommu)); c = " "; } rid = 0; #ifdef IOMMU iommu_get_requester(dev, &rid); #endif if (rid != 0) sbuf_printf(&sb, "%srid=%#x", c, rid); break; default: error = EINVAL; goto out; } error = sbuf_finish(&sb); out: bus_topo_unlock(); sbuf_delete(&sb); return (error); } static void device_sysctl_init(device_t dev) { devclass_t dc = dev->devclass; int domain; if (dev->sysctl_tree != NULL) return; devclass_sysctl_init(dc); sysctl_ctx_init(&dev->sysctl_ctx); dev->sysctl_tree = SYSCTL_ADD_NODE_WITH_LABEL(&dev->sysctl_ctx, SYSCTL_CHILDREN(dc->sysctl_tree), OID_AUTO, dev->nameunit + strlen(dc->name), CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "", "device_index"); SYSCTL_ADD_PROC(&dev->sysctl_ctx, SYSCTL_CHILDREN(dev->sysctl_tree), OID_AUTO, "%desc", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, DEVICE_SYSCTL_DESC, device_sysctl_handler, "A", "device description"); SYSCTL_ADD_PROC(&dev->sysctl_ctx, SYSCTL_CHILDREN(dev->sysctl_tree), OID_AUTO, "%driver", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, DEVICE_SYSCTL_DRIVER, device_sysctl_handler, "A", "device driver name"); SYSCTL_ADD_PROC(&dev->sysctl_ctx, SYSCTL_CHILDREN(dev->sysctl_tree), OID_AUTO, "%location", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, DEVICE_SYSCTL_LOCATION, device_sysctl_handler, "A", "device location relative to parent"); SYSCTL_ADD_PROC(&dev->sysctl_ctx, SYSCTL_CHILDREN(dev->sysctl_tree), OID_AUTO, "%pnpinfo", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, DEVICE_SYSCTL_PNPINFO, device_sysctl_handler, "A", "device identification"); SYSCTL_ADD_PROC(&dev->sysctl_ctx, SYSCTL_CHILDREN(dev->sysctl_tree), OID_AUTO, "%parent", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, DEVICE_SYSCTL_PARENT, device_sysctl_handler, "A", "parent device"); SYSCTL_ADD_PROC(&dev->sysctl_ctx, SYSCTL_CHILDREN(dev->sysctl_tree), OID_AUTO, "%iommu", CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, DEVICE_SYSCTL_IOMMU, device_sysctl_handler, "A", "iommu unit handling the device requests"); if (bus_get_domain(dev, &domain) == 0) SYSCTL_ADD_INT(&dev->sysctl_ctx, SYSCTL_CHILDREN(dev->sysctl_tree), OID_AUTO, "%domain", CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, domain, "NUMA domain"); } static void device_sysctl_update(device_t dev) { devclass_t dc = dev->devclass; if (dev->sysctl_tree == NULL) return; sysctl_rename_oid(dev->sysctl_tree, dev->nameunit + strlen(dc->name)); } static void device_sysctl_fini(device_t dev) { if (dev->sysctl_tree == NULL) return; sysctl_ctx_free(&dev->sysctl_ctx); dev->sysctl_tree = NULL; } static struct device_list bus_data_devices; static int bus_data_generation = 1; static kobj_method_t null_methods[] = { KOBJMETHOD_END }; DEFINE_CLASS(null, null_methods, 0); void bus_topo_assert(void) { GIANT_REQUIRED; } struct mtx * bus_topo_mtx(void) { return (&Giant); } void bus_topo_lock(void) { mtx_lock(bus_topo_mtx()); } void bus_topo_unlock(void) { mtx_unlock(bus_topo_mtx()); } /* * Bus pass implementation */ static driver_list_t passes = TAILQ_HEAD_INITIALIZER(passes); static int bus_current_pass = BUS_PASS_ROOT; /** * @internal * @brief Register the pass level of a new driver attachment * * Register a new driver attachment's pass level. If no driver * attachment with the same pass level has been added, then @p new * will be added to the global passes list. * * @param new the new driver attachment */ static void driver_register_pass(struct driverlink *new) { struct driverlink *dl; /* We only consider pass numbers during boot. */ if (bus_current_pass == BUS_PASS_DEFAULT) return; /* * Walk the passes list. If we already know about this pass * then there is nothing to do. If we don't, then insert this * driver link into the list. */ TAILQ_FOREACH(dl, &passes, passlink) { if (dl->pass < new->pass) continue; if (dl->pass == new->pass) return; TAILQ_INSERT_BEFORE(dl, new, passlink); return; } TAILQ_INSERT_TAIL(&passes, new, passlink); } /** * @brief Retrieve the current bus pass * * Retrieves the current bus pass level. Call the BUS_NEW_PASS() * method on the root bus to kick off a new device tree scan for each * new pass level that has at least one driver. */ int bus_get_pass(void) { return (bus_current_pass); } /** * @brief Raise the current bus pass * * Raise the current bus pass level to @p pass. Call the BUS_NEW_PASS() * method on the root bus to kick off a new device tree scan for each * new pass level that has at least one driver. */ static void bus_set_pass(int pass) { struct driverlink *dl; if (bus_current_pass > pass) panic("Attempt to lower bus pass level"); TAILQ_FOREACH(dl, &passes, passlink) { /* Skip pass values below the current pass level. */ if (dl->pass <= bus_current_pass) continue; /* * Bail once we hit a driver with a pass level that is * too high. */ if (dl->pass > pass) break; /* * Raise the pass level to the next level and rescan * the tree. */ bus_current_pass = dl->pass; BUS_NEW_PASS(root_bus); } /* * If there isn't a driver registered for the requested pass, * then bus_current_pass might still be less than 'pass'. Set * it to 'pass' in that case. */ if (bus_current_pass < pass) bus_current_pass = pass; KASSERT(bus_current_pass == pass, ("Failed to update bus pass level")); } /* * Devclass implementation */ static devclass_list_t devclasses = TAILQ_HEAD_INITIALIZER(devclasses); /** * @internal * @brief Find or create a device class * * If a device class with the name @p classname exists, return it, * otherwise if @p create is non-zero create and return a new device * class. * * If @p parentname is non-NULL, the parent of the devclass is set to * the devclass of that name. * * @param classname the devclass name to find or create * @param parentname the parent devclass name or @c NULL * @param create non-zero to create a devclass */ static devclass_t devclass_find_internal(const char *classname, const char *parentname, int create) { devclass_t dc; PDEBUG(("looking for %s", classname)); if (!classname) return (NULL); TAILQ_FOREACH(dc, &devclasses, link) { if (!strcmp(dc->name, classname)) break; } if (create && !dc) { PDEBUG(("creating %s", classname)); dc = malloc(sizeof(struct devclass) + strlen(classname) + 1, M_BUS, M_WAITOK | M_ZERO); dc->parent = NULL; dc->name = (char*) (dc + 1); strcpy(dc->name, classname); TAILQ_INIT(&dc->drivers); TAILQ_INSERT_TAIL(&devclasses, dc, link); bus_data_generation_update(); } /* * If a parent class is specified, then set that as our parent so * that this devclass will support drivers for the parent class as * well. If the parent class has the same name don't do this though * as it creates a cycle that can trigger an infinite loop in * device_probe_child() if a device exists for which there is no * suitable driver. */ if (parentname && dc && !dc->parent && strcmp(classname, parentname) != 0) { dc->parent = devclass_find_internal(parentname, NULL, TRUE); dc->parent->flags |= DC_HAS_CHILDREN; } return (dc); } /** * @brief Create a device class * * If a device class with the name @p classname exists, return it, * otherwise create and return a new device class. * * @param classname the devclass name to find or create */ devclass_t devclass_create(const char *classname) { return (devclass_find_internal(classname, NULL, TRUE)); } /** * @brief Find a device class * * If a device class with the name @p classname exists, return it, * otherwise return @c NULL. * * @param classname the devclass name to find */ devclass_t devclass_find(const char *classname) { return (devclass_find_internal(classname, NULL, FALSE)); } /** * @brief Register that a device driver has been added to a devclass * * Register that a device driver has been added to a devclass. This * is called by devclass_add_driver to accomplish the recursive * notification of all the children classes of dc, as well as dc. * Each layer will have BUS_DRIVER_ADDED() called for all instances of * the devclass. * * We do a full search here of the devclass list at each iteration * level to save storing children-lists in the devclass structure. If * we ever move beyond a few dozen devices doing this, we may need to * reevaluate... * * @param dc the devclass to edit * @param driver the driver that was just added */ static void devclass_driver_added(devclass_t dc, driver_t *driver) { devclass_t parent; int i; /* * Call BUS_DRIVER_ADDED for any existing buses in this class. */ for (i = 0; i < dc->maxunit; i++) if (dc->devices[i] && device_is_attached(dc->devices[i])) BUS_DRIVER_ADDED(dc->devices[i], driver); /* * Walk through the children classes. Since we only keep a * single parent pointer around, we walk the entire list of * devclasses looking for children. We set the * DC_HAS_CHILDREN flag when a child devclass is created on * the parent, so we only walk the list for those devclasses * that have children. */ if (!(dc->flags & DC_HAS_CHILDREN)) return; parent = dc; TAILQ_FOREACH(dc, &devclasses, link) { if (dc->parent == parent) devclass_driver_added(dc, driver); } } static void device_handle_nomatch(device_t dev) { BUS_PROBE_NOMATCH(dev->parent, dev); EVENTHANDLER_DIRECT_INVOKE(device_nomatch, dev); dev->flags |= DF_DONENOMATCH; } /** * @brief Add a device driver to a device class * * Add a device driver to a devclass. This is normally called * automatically by DRIVER_MODULE(). The BUS_DRIVER_ADDED() method of * all devices in the devclass will be called to allow them to attempt * to re-probe any unmatched children. * * @param dc the devclass to edit * @param driver the driver to register */ int devclass_add_driver(devclass_t dc, driver_t *driver, int pass, devclass_t *dcp) { driverlink_t dl; devclass_t child_dc; const char *parentname; PDEBUG(("%s", DRIVERNAME(driver))); /* Don't allow invalid pass values. */ if (pass <= BUS_PASS_ROOT) return (EINVAL); dl = malloc(sizeof *dl, M_BUS, M_WAITOK|M_ZERO); /* * Compile the driver's methods. Also increase the reference count * so that the class doesn't get freed when the last instance * goes. This means we can safely use static methods and avoids a * double-free in devclass_delete_driver. */ kobj_class_compile((kobj_class_t) driver); /* * If the driver has any base classes, make the * devclass inherit from the devclass of the driver's * first base class. This will allow the system to * search for drivers in both devclasses for children * of a device using this driver. */ if (driver->baseclasses) parentname = driver->baseclasses[0]->name; else parentname = NULL; child_dc = devclass_find_internal(driver->name, parentname, TRUE); if (dcp != NULL) *dcp = child_dc; dl->driver = driver; TAILQ_INSERT_TAIL(&dc->drivers, dl, link); driver->refs++; /* XXX: kobj_mtx */ dl->pass = pass; driver_register_pass(dl); if (device_frozen) { dl->flags |= DL_DEFERRED_PROBE; } else { devclass_driver_added(dc, driver); } bus_data_generation_update(); return (0); } /** * @brief Register that a device driver has been deleted from a devclass * * Register that a device driver has been removed from a devclass. * This is called by devclass_delete_driver to accomplish the * recursive notification of all the children classes of busclass, as * well as busclass. Each layer will attempt to detach the driver * from any devices that are children of the bus's devclass. The function * will return an error if a device fails to detach. * * We do a full search here of the devclass list at each iteration * level to save storing children-lists in the devclass structure. If * we ever move beyond a few dozen devices doing this, we may need to * reevaluate... * * @param busclass the devclass of the parent bus * @param dc the devclass of the driver being deleted * @param driver the driver being deleted */ static int devclass_driver_deleted(devclass_t busclass, devclass_t dc, driver_t *driver) { devclass_t parent; device_t dev; int error, i; /* * Disassociate from any devices. We iterate through all the * devices in the devclass of the driver and detach any which are * using the driver and which have a parent in the devclass which * we are deleting from. * * Note that since a driver can be in multiple devclasses, we * should not detach devices which are not children of devices in * the affected devclass. * * If we're frozen, we don't generate NOMATCH events. Mark to * generate later. */ for (i = 0; i < dc->maxunit; i++) { if (dc->devices[i]) { dev = dc->devices[i]; if (dev->driver == driver && dev->parent && dev->parent->devclass == busclass) { if ((error = device_detach(dev)) != 0) return (error); if (device_frozen) { dev->flags &= ~DF_DONENOMATCH; dev->flags |= DF_NEEDNOMATCH; } else { device_handle_nomatch(dev); } } } } /* * Walk through the children classes. Since we only keep a * single parent pointer around, we walk the entire list of * devclasses looking for children. We set the * DC_HAS_CHILDREN flag when a child devclass is created on * the parent, so we only walk the list for those devclasses * that have children. */ if (!(busclass->flags & DC_HAS_CHILDREN)) return (0); parent = busclass; TAILQ_FOREACH(busclass, &devclasses, link) { if (busclass->parent == parent) { error = devclass_driver_deleted(busclass, dc, driver); if (error) return (error); } } return (0); } /** * @brief Delete a device driver from a device class * * Delete a device driver from a devclass. This is normally called * automatically by DRIVER_MODULE(). * * If the driver is currently attached to any devices, * devclass_delete_driver() will first attempt to detach from each * device. If one of the detach calls fails, the driver will not be * deleted. * * @param dc the devclass to edit * @param driver the driver to unregister */ int devclass_delete_driver(devclass_t busclass, driver_t *driver) { devclass_t dc = devclass_find(driver->name); driverlink_t dl; int error; PDEBUG(("%s from devclass %s", driver->name, DEVCLANAME(busclass))); if (!dc) return (0); /* * Find the link structure in the bus' list of drivers. */ TAILQ_FOREACH(dl, &busclass->drivers, link) { if (dl->driver == driver) break; } if (!dl) { PDEBUG(("%s not found in %s list", driver->name, busclass->name)); return (ENOENT); } error = devclass_driver_deleted(busclass, dc, driver); if (error != 0) return (error); TAILQ_REMOVE(&busclass->drivers, dl, link); free(dl, M_BUS); /* XXX: kobj_mtx */ driver->refs--; if (driver->refs == 0) kobj_class_free((kobj_class_t) driver); bus_data_generation_update(); return (0); } /** * @brief Quiesces a set of device drivers from a device class * * Quiesce a device driver from a devclass. This is normally called * automatically by DRIVER_MODULE(). * * If the driver is currently attached to any devices, * devclass_quiesece_driver() will first attempt to quiesce each * device. * * @param dc the devclass to edit * @param driver the driver to unregister */ static int devclass_quiesce_driver(devclass_t busclass, driver_t *driver) { devclass_t dc = devclass_find(driver->name); driverlink_t dl; device_t dev; int i; int error; PDEBUG(("%s from devclass %s", driver->name, DEVCLANAME(busclass))); if (!dc) return (0); /* * Find the link structure in the bus' list of drivers. */ TAILQ_FOREACH(dl, &busclass->drivers, link) { if (dl->driver == driver) break; } if (!dl) { PDEBUG(("%s not found in %s list", driver->name, busclass->name)); return (ENOENT); } /* * Quiesce all devices. We iterate through all the devices in * the devclass of the driver and quiesce any which are using * the driver and which have a parent in the devclass which we * are quiescing. * * Note that since a driver can be in multiple devclasses, we * should not quiesce devices which are not children of * devices in the affected devclass. */ for (i = 0; i < dc->maxunit; i++) { if (dc->devices[i]) { dev = dc->devices[i]; if (dev->driver == driver && dev->parent && dev->parent->devclass == busclass) { if ((error = device_quiesce(dev)) != 0) return (error); } } } return (0); } /** * @internal */ static driverlink_t devclass_find_driver_internal(devclass_t dc, const char *classname) { driverlink_t dl; PDEBUG(("%s in devclass %s", classname, DEVCLANAME(dc))); TAILQ_FOREACH(dl, &dc->drivers, link) { if (!strcmp(dl->driver->name, classname)) return (dl); } PDEBUG(("not found")); return (NULL); } /** * @brief Return the name of the devclass */ const char * devclass_get_name(devclass_t dc) { return (dc->name); } /** * @brief Find a device given a unit number * * @param dc the devclass to search * @param unit the unit number to search for * * @returns the device with the given unit number or @c * NULL if there is no such device */ device_t devclass_get_device(devclass_t dc, int unit) { if (dc == NULL || unit < 0 || unit >= dc->maxunit) return (NULL); return (dc->devices[unit]); } /** * @brief Find the softc field of a device given a unit number * * @param dc the devclass to search * @param unit the unit number to search for * * @returns the softc field of the device with the given * unit number or @c NULL if there is no such * device */ void * devclass_get_softc(devclass_t dc, int unit) { device_t dev; dev = devclass_get_device(dc, unit); if (!dev) return (NULL); return (device_get_softc(dev)); } /** * @brief Get a list of devices in the devclass * * An array containing a list of all the devices in the given devclass * is allocated and returned in @p *devlistp. The number of devices * in the array is returned in @p *devcountp. The caller should free * the array using @c free(p, M_TEMP), even if @p *devcountp is 0. * * @param dc the devclass to examine * @param devlistp points at location for array pointer return * value * @param devcountp points at location for array size return value * * @retval 0 success * @retval ENOMEM the array allocation failed */ int devclass_get_devices(devclass_t dc, device_t **devlistp, int *devcountp) { int count, i; device_t *list; count = devclass_get_count(dc); list = malloc(count * sizeof(device_t), M_TEMP, M_NOWAIT|M_ZERO); if (!list) return (ENOMEM); count = 0; for (i = 0; i < dc->maxunit; i++) { if (dc->devices[i]) { list[count] = dc->devices[i]; count++; } } *devlistp = list; *devcountp = count; return (0); } /** * @brief Get a list of drivers in the devclass * * An array containing a list of pointers to all the drivers in the * given devclass is allocated and returned in @p *listp. The number * of drivers in the array is returned in @p *countp. The caller should * free the array using @c free(p, M_TEMP). * * @param dc the devclass to examine * @param listp gives location for array pointer return value * @param countp gives location for number of array elements * return value * * @retval 0 success * @retval ENOMEM the array allocation failed */ int devclass_get_drivers(devclass_t dc, driver_t ***listp, int *countp) { driverlink_t dl; driver_t **list; int count; count = 0; TAILQ_FOREACH(dl, &dc->drivers, link) count++; list = malloc(count * sizeof(driver_t *), M_TEMP, M_NOWAIT); if (list == NULL) return (ENOMEM); count = 0; TAILQ_FOREACH(dl, &dc->drivers, link) { list[count] = dl->driver; count++; } *listp = list; *countp = count; return (0); } /** * @brief Get the number of devices in a devclass * * @param dc the devclass to examine */ int devclass_get_count(devclass_t dc) { int count, i; count = 0; for (i = 0; i < dc->maxunit; i++) if (dc->devices[i]) count++; return (count); } /** * @brief Get the maximum unit number used in a devclass * * Note that this is one greater than the highest currently-allocated unit. If * @p dc is NULL, @c -1 is returned to indicate that not even the devclass has * been allocated yet. * * @param dc the devclass to examine */ int devclass_get_maxunit(devclass_t dc) { if (dc == NULL) return (-1); return (dc->maxunit); } /** * @brief Find a free unit number in a devclass * * This function searches for the first unused unit number greater * that or equal to @p unit. Note: This can return INT_MAX which * may be rejected elsewhere. * * @param dc the devclass to examine * @param unit the first unit number to check */ int devclass_find_free_unit(devclass_t dc, int unit) { if (dc == NULL) return (unit); while (unit < dc->maxunit && dc->devices[unit] != NULL) unit++; return (unit); } /** * @brief Set the parent of a devclass * * The parent class is normally initialised automatically by * DRIVER_MODULE(). * * @param dc the devclass to edit * @param pdc the new parent devclass */ void devclass_set_parent(devclass_t dc, devclass_t pdc) { dc->parent = pdc; } /** * @brief Get the parent of a devclass * * @param dc the devclass to examine */ devclass_t devclass_get_parent(devclass_t dc) { return (dc->parent); } struct sysctl_ctx_list * devclass_get_sysctl_ctx(devclass_t dc) { return (&dc->sysctl_ctx); } struct sysctl_oid * devclass_get_sysctl_tree(devclass_t dc) { return (dc->sysctl_tree); } /** * @internal * @brief Allocate a unit number * * On entry, @p *unitp is the desired unit number (or @c DEVICE_UNIT_ANY if any * will do). The allocated unit number is returned in @p *unitp. * * @param dc the devclass to allocate from * @param unitp points at the location for the allocated unit * number * * @retval 0 success * @retval EEXIST the requested unit number is already allocated * @retval ENOMEM memory allocation failure * @retval EINVAL unit is negative or we've run out of units */ static int devclass_alloc_unit(devclass_t dc, device_t dev, int *unitp) { const char *s; int unit = *unitp; PDEBUG(("unit %d in devclass %s", unit, DEVCLANAME(dc))); /* Ask the parent bus if it wants to wire this device. */ if (unit == DEVICE_UNIT_ANY) BUS_HINT_DEVICE_UNIT(device_get_parent(dev), dev, dc->name, &unit); /* Unit numbers are either DEVICE_UNIT_ANY or in [0,INT_MAX) */ if ((unit < 0 && unit != DEVICE_UNIT_ANY) || unit == INT_MAX) return (EINVAL); /* If we were given a wired unit number, check for existing device */ if (unit != DEVICE_UNIT_ANY) { if (unit < dc->maxunit && dc->devices[unit] != NULL) { if (bootverbose) printf("%s: %s%d already exists; skipping it\n", dc->name, dc->name, *unitp); return (EEXIST); } } else { /* Unwired device, find the next available slot for it */ unit = 0; for (unit = 0; unit < INT_MAX; unit++) { /* If this device slot is already in use, skip it. */ if (unit < dc->maxunit && dc->devices[unit] != NULL) continue; /* If there is an "at" hint for a unit then skip it. */ if (resource_string_value(dc->name, unit, "at", &s) == 0) continue; break; } } /* * Unit numbers must be in the range [0, INT_MAX), so exclude INT_MAX as * too large. We constrain maxunit below to be <= INT_MAX. This means we * can treat unit and maxunit as normal integers with normal math * everywhere and we only have to flag INT_MAX as invalid. */ if (unit == INT_MAX) return (EINVAL); /* * We've selected a unit beyond the length of the table, so let's extend * the table to make room for all units up to and including this one. */ if (unit >= dc->maxunit) { int newsize; newsize = unit + 1; dc->devices = reallocf(dc->devices, newsize * sizeof(*dc->devices), M_BUS, M_WAITOK); memset(dc->devices + dc->maxunit, 0, sizeof(device_t) * (newsize - dc->maxunit)); dc->maxunit = newsize; } PDEBUG(("now: unit %d in devclass %s", unit, DEVCLANAME(dc))); *unitp = unit; return (0); } /** * @internal * @brief Add a device to a devclass * * A unit number is allocated for the device (using the device's * preferred unit number if any) and the device is registered in the * devclass. This allows the device to be looked up by its unit * number, e.g. by decoding a dev_t minor number. * * @param dc the devclass to add to * @param dev the device to add * * @retval 0 success * @retval EEXIST the requested unit number is already allocated * @retval ENOMEM memory allocation failure * @retval EINVAL Unit number invalid or too many units */ static int devclass_add_device(devclass_t dc, device_t dev) { int buflen, error; PDEBUG(("%s in devclass %s", DEVICENAME(dev), DEVCLANAME(dc))); buflen = snprintf(NULL, 0, "%s%d$", dc->name, INT_MAX); if (buflen < 0) return (ENOMEM); dev->nameunit = malloc(buflen, M_BUS, M_WAITOK|M_ZERO); if ((error = devclass_alloc_unit(dc, dev, &dev->unit)) != 0) { free(dev->nameunit, M_BUS); dev->nameunit = NULL; return (error); } dc->devices[dev->unit] = dev; dev->devclass = dc; snprintf(dev->nameunit, buflen, "%s%d", dc->name, dev->unit); return (0); } /** * @internal * @brief Delete a device from a devclass * * The device is removed from the devclass's device list and its unit * number is freed. * @param dc the devclass to delete from * @param dev the device to delete * * @retval 0 success */ static int devclass_delete_device(devclass_t dc, device_t dev) { if (!dc || !dev) return (0); PDEBUG(("%s in devclass %s", DEVICENAME(dev), DEVCLANAME(dc))); if (dev->devclass != dc || dc->devices[dev->unit] != dev) panic("devclass_delete_device: inconsistent device class"); dc->devices[dev->unit] = NULL; if (dev->flags & DF_WILDCARD) dev->unit = DEVICE_UNIT_ANY; dev->devclass = NULL; free(dev->nameunit, M_BUS); dev->nameunit = NULL; return (0); } /** * @internal * @brief Make a new device and add it as a child of @p parent * * @param parent the parent of the new device * @param name the devclass name of the new device or @c NULL * to leave the devclass unspecified * @parem unit the unit number of the new device of @c DEVICE_UNIT_ANY * to leave the unit number unspecified * * @returns the new device */ static device_t make_device(device_t parent, const char *name, int unit) { device_t dev; devclass_t dc; PDEBUG(("%s at %s as unit %d", name, DEVICENAME(parent), unit)); if (name) { dc = devclass_find_internal(name, NULL, TRUE); if (!dc) { printf("make_device: can't find device class %s\n", name); return (NULL); } } else { dc = NULL; } dev = malloc(sizeof(*dev), M_BUS, M_WAITOK|M_ZERO); dev->parent = parent; TAILQ_INIT(&dev->children); kobj_init((kobj_t) dev, &null_class); dev->driver = NULL; dev->devclass = NULL; dev->unit = unit; dev->nameunit = NULL; dev->desc = NULL; dev->busy = 0; dev->devflags = 0; dev->flags = DF_ENABLED; dev->order = 0; if (unit == DEVICE_UNIT_ANY) dev->flags |= DF_WILDCARD; if (name) { dev->flags |= DF_FIXEDCLASS; if (devclass_add_device(dc, dev)) { kobj_delete((kobj_t) dev, M_BUS); return (NULL); } } if (parent != NULL && device_has_quiet_children(parent)) dev->flags |= DF_QUIET | DF_QUIET_CHILDREN; dev->ivars = NULL; dev->softc = NULL; LIST_INIT(&dev->props); dev->state = DS_NOTPRESENT; TAILQ_INSERT_TAIL(&bus_data_devices, dev, devlink); bus_data_generation_update(); return (dev); } /** * @internal * @brief Print a description of a device. */ static int device_print_child(device_t dev, device_t child) { int retval = 0; if (device_is_alive(child)) retval += BUS_PRINT_CHILD(dev, child); else retval += device_printf(child, " not found\n"); return (retval); } /** * @brief Create a new device * * This creates a new device and adds it as a child of an existing * parent device. The new device will be added after the last existing * child with order zero. * * @param dev the device which will be the parent of the * new child device * @param name devclass name for new device or @c NULL if not * specified * @param unit unit number for new device or @c DEVICE_UNIT_ANY if not * specified * * @returns the new device */ device_t device_add_child(device_t dev, const char *name, int unit) { return (device_add_child_ordered(dev, 0, name, unit)); } /** * @brief Create a new device * * This creates a new device and adds it as a child of an existing * parent device. The new device will be added after the last existing * child with the same order. * * @param dev the device which will be the parent of the * new child device * @param order a value which is used to partially sort the * children of @p dev - devices created using * lower values of @p order appear first in @p * dev's list of children * @param name devclass name for new device or @c NULL if not * specified * @param unit unit number for new device or @c DEVICE_UNIT_ANY if not * specified * * @returns the new device */ device_t device_add_child_ordered(device_t dev, u_int order, const char *name, int unit) { device_t child; device_t place; PDEBUG(("%s at %s with order %u as unit %d", name, DEVICENAME(dev), order, unit)); KASSERT(name != NULL || unit == DEVICE_UNIT_ANY, ("child device with wildcard name and specific unit number")); child = make_device(dev, name, unit); if (child == NULL) return (child); child->order = order; TAILQ_FOREACH(place, &dev->children, link) { if (place->order > order) break; } if (place) { /* * The device 'place' is the first device whose order is * greater than the new child. */ TAILQ_INSERT_BEFORE(place, child, link); } else { /* * The new child's order is greater or equal to the order of * any existing device. Add the child to the tail of the list. */ TAILQ_INSERT_TAIL(&dev->children, child, link); } bus_data_generation_update(); return (child); } /** * @brief Delete a device * * This function deletes a device along with all of its children. If * the device currently has a driver attached to it, the device is * detached first using device_detach(). * * @param dev the parent device * @param child the device to delete * * @retval 0 success * @retval non-zero a unit error code describing the error */ int device_delete_child(device_t dev, device_t child) { int error; device_t grandchild; PDEBUG(("%s from %s", DEVICENAME(child), DEVICENAME(dev))); /* * Detach child. Ideally this cleans up any grandchild * devices. */ if ((error = device_detach(child)) != 0) return (error); /* Delete any grandchildren left after detach. */ while ((grandchild = TAILQ_FIRST(&child->children)) != NULL) { error = device_delete_child(child, grandchild); if (error) return (error); } device_destroy_props(dev); if (child->devclass) devclass_delete_device(child->devclass, child); if (child->parent) BUS_CHILD_DELETED(dev, child); TAILQ_REMOVE(&dev->children, child, link); TAILQ_REMOVE(&bus_data_devices, child, devlink); kobj_delete((kobj_t) child, M_BUS); bus_data_generation_update(); return (0); } /** * @brief Delete all children devices of the given device, if any. * * This function deletes all children devices of the given device, if * any, using the device_delete_child() function for each device it * finds. If a child device cannot be deleted, this function will * return an error code. * * @param dev the parent device * * @retval 0 success * @retval non-zero a device would not detach */ int device_delete_children(device_t dev) { device_t child; int error; PDEBUG(("Deleting all children of %s", DEVICENAME(dev))); error = 0; while ((child = TAILQ_FIRST(&dev->children)) != NULL) { error = device_delete_child(dev, child); if (error) { PDEBUG(("Failed deleting %s", DEVICENAME(child))); break; } } return (error); } /** * @brief Find a device given a unit number * * This is similar to devclass_get_devices() but only searches for * devices which have @p dev as a parent. * * @param dev the parent device to search * @param unit the unit number to search for. If the unit is * @c DEVICE_UNIT_ANY, return the first child of @p dev * which has name @p classname (that is, the one with the * lowest unit.) * * @returns the device with the given unit number or @c * NULL if there is no such device */ device_t device_find_child(device_t dev, const char *classname, int unit) { devclass_t dc; device_t child; dc = devclass_find(classname); if (!dc) return (NULL); if (unit != DEVICE_UNIT_ANY) { child = devclass_get_device(dc, unit); if (child && child->parent == dev) return (child); } else { for (unit = 0; unit < devclass_get_maxunit(dc); unit++) { child = devclass_get_device(dc, unit); if (child && child->parent == dev) return (child); } } return (NULL); } /** * @internal */ static driverlink_t first_matching_driver(devclass_t dc, device_t dev) { if (dev->devclass) return (devclass_find_driver_internal(dc, dev->devclass->name)); return (TAILQ_FIRST(&dc->drivers)); } /** * @internal */ static driverlink_t next_matching_driver(devclass_t dc, device_t dev, driverlink_t last) { if (dev->devclass) { driverlink_t dl; for (dl = TAILQ_NEXT(last, link); dl; dl = TAILQ_NEXT(dl, link)) if (!strcmp(dev->devclass->name, dl->driver->name)) return (dl); return (NULL); } return (TAILQ_NEXT(last, link)); } /** * @internal */ int device_probe_child(device_t dev, device_t child) { devclass_t dc; driverlink_t best = NULL; driverlink_t dl; int result, pri = 0; /* We should preserve the devclass (or lack of) set by the bus. */ int hasclass = (child->devclass != NULL); bus_topo_assert(); dc = dev->devclass; if (!dc) panic("device_probe_child: parent device has no devclass"); /* * If the state is already probed, then return. */ if (child->state == DS_ALIVE) return (0); for (; dc; dc = dc->parent) { for (dl = first_matching_driver(dc, child); dl; dl = next_matching_driver(dc, child, dl)) { /* If this driver's pass is too high, then ignore it. */ if (dl->pass > bus_current_pass) continue; PDEBUG(("Trying %s", DRIVERNAME(dl->driver))); result = device_set_driver(child, dl->driver); if (result == ENOMEM) return (result); else if (result != 0) continue; if (!hasclass) { if (device_set_devclass(child, dl->driver->name) != 0) { char const * devname = device_get_name(child); if (devname == NULL) devname = "(unknown)"; printf("driver bug: Unable to set " "devclass (class: %s " "devname: %s)\n", dl->driver->name, devname); (void)device_set_driver(child, NULL); continue; } } /* Fetch any flags for the device before probing. */ resource_int_value(dl->driver->name, child->unit, "flags", &child->devflags); result = DEVICE_PROBE(child); /* * If probe returns 0, this is the driver that wins this * device. */ if (result == 0) { best = dl; pri = 0; goto exact_match; /* C doesn't have break 2 */ } /* Reset flags and devclass before the next probe. */ child->devflags = 0; if (!hasclass) (void)device_set_devclass(child, NULL); /* * Reset DF_QUIET in case this driver doesn't * end up as the best driver. */ device_verbose(child); /* * Probes that return BUS_PROBE_NOWILDCARD or lower * only match on devices whose driver was explicitly * specified. */ if (result <= BUS_PROBE_NOWILDCARD && !(child->flags & DF_FIXEDCLASS)) { result = ENXIO; } /* * The driver returned an error so it * certainly doesn't match. */ if (result > 0) { (void)device_set_driver(child, NULL); continue; } /* * A priority lower than SUCCESS, remember the * best matching driver. Initialise the value * of pri for the first match. */ if (best == NULL || result > pri) { best = dl; pri = result; continue; } } } if (best == NULL) return (ENXIO); /* * If we found a driver, change state and initialise the devclass. * Set the winning driver, devclass, and flags. */ result = device_set_driver(child, best->driver); if (result != 0) return (result); if (!child->devclass) { result = device_set_devclass(child, best->driver->name); if (result != 0) { (void)device_set_driver(child, NULL); return (result); } } resource_int_value(best->driver->name, child->unit, "flags", &child->devflags); /* * A bit bogus. Call the probe method again to make sure that we have * the right description for the device. */ result = DEVICE_PROBE(child); if (result > 0) { if (!hasclass) (void)device_set_devclass(child, NULL); (void)device_set_driver(child, NULL); return (result); } exact_match: child->state = DS_ALIVE; bus_data_generation_update(); return (0); } /** * @brief Return the parent of a device */ device_t device_get_parent(device_t dev) { return (dev->parent); } /** * @brief Get a list of children of a device * * An array containing a list of all the children of the given device * is allocated and returned in @p *devlistp. The number of devices * in the array is returned in @p *devcountp. The caller should free * the array using @c free(p, M_TEMP). * * @param dev the device to examine * @param devlistp points at location for array pointer return * value * @param devcountp points at location for array size return value * * @retval 0 success * @retval ENOMEM the array allocation failed */ int device_get_children(device_t dev, device_t **devlistp, int *devcountp) { int count; device_t child; device_t *list; count = 0; TAILQ_FOREACH(child, &dev->children, link) { count++; } if (count == 0) { *devlistp = NULL; *devcountp = 0; return (0); } list = malloc(count * sizeof(device_t), M_TEMP, M_NOWAIT|M_ZERO); if (!list) return (ENOMEM); count = 0; TAILQ_FOREACH(child, &dev->children, link) { list[count] = child; count++; } *devlistp = list; *devcountp = count; return (0); } /** * @brief Return the current driver for the device or @c NULL if there * is no driver currently attached */ driver_t * device_get_driver(device_t dev) { return (dev->driver); } /** * @brief Return the current devclass for the device or @c NULL if * there is none. */ devclass_t device_get_devclass(device_t dev) { return (dev->devclass); } /** * @brief Return the name of the device's devclass or @c NULL if there * is none. */ const char * device_get_name(device_t dev) { if (dev != NULL && dev->devclass) return (devclass_get_name(dev->devclass)); return (NULL); } /** * @brief Return a string containing the device's devclass name * followed by an ascii representation of the device's unit number * (e.g. @c "foo2"). */ const char * device_get_nameunit(device_t dev) { return (dev->nameunit); } /** * @brief Return the device's unit number. */ int device_get_unit(device_t dev) { return (dev->unit); } /** * @brief Return the device's description string */ const char * device_get_desc(device_t dev) { return (dev->desc); } /** * @brief Return the device's flags */ uint32_t device_get_flags(device_t dev) { return (dev->devflags); } struct sysctl_ctx_list * device_get_sysctl_ctx(device_t dev) { return (&dev->sysctl_ctx); } struct sysctl_oid * device_get_sysctl_tree(device_t dev) { return (dev->sysctl_tree); } /** * @brief Print the name of the device followed by a colon and a space * * @returns the number of characters printed */ int device_print_prettyname(device_t dev) { const char *name = device_get_name(dev); if (name == NULL) return (printf("unknown: ")); return (printf("%s%d: ", name, device_get_unit(dev))); } /** * @brief Print the name of the device followed by a colon, a space * and the result of calling vprintf() with the value of @p fmt and * the following arguments. * * @returns the number of characters printed */ int device_printf(device_t dev, const char * fmt, ...) { char buf[128]; struct sbuf sb; const char *name; va_list ap; size_t retval; retval = 0; sbuf_new(&sb, buf, sizeof(buf), SBUF_FIXEDLEN); sbuf_set_drain(&sb, sbuf_printf_drain, &retval); name = device_get_name(dev); if (name == NULL) sbuf_cat(&sb, "unknown: "); else sbuf_printf(&sb, "%s%d: ", name, device_get_unit(dev)); va_start(ap, fmt); sbuf_vprintf(&sb, fmt, ap); va_end(ap); sbuf_finish(&sb); sbuf_delete(&sb); return (retval); } /** * @brief Print the name of the device followed by a colon, a space * and the result of calling log() with the value of @p fmt and * the following arguments. * * @returns the number of characters printed */ int device_log(device_t dev, int pri, const char * fmt, ...) { char buf[128]; struct sbuf sb; const char *name; va_list ap; size_t retval; retval = 0; sbuf_new(&sb, buf, sizeof(buf), SBUF_FIXEDLEN); name = device_get_name(dev); if (name == NULL) sbuf_cat(&sb, "unknown: "); else sbuf_printf(&sb, "%s%d: ", name, device_get_unit(dev)); va_start(ap, fmt); sbuf_vprintf(&sb, fmt, ap); va_end(ap); sbuf_finish(&sb); log(pri, "%.*s", (int) sbuf_len(&sb), sbuf_data(&sb)); retval = sbuf_len(&sb); sbuf_delete(&sb); return (retval); } /** * @internal */ static void device_set_desc_internal(device_t dev, const char *desc, bool allocated) { if (dev->desc && (dev->flags & DF_DESCMALLOCED)) { free(dev->desc, M_BUS); dev->flags &= ~DF_DESCMALLOCED; dev->desc = NULL; } if (allocated && desc) dev->flags |= DF_DESCMALLOCED; dev->desc = __DECONST(char *, desc); bus_data_generation_update(); } /** * @brief Set the device's description * * The value of @c desc should be a string constant that will not * change (at least until the description is changed in a subsequent * call to device_set_desc() or device_set_desc_copy()). */ void device_set_desc(device_t dev, const char *desc) { device_set_desc_internal(dev, desc, false); } /** * @brief Set the device's description * * A printf-like version of device_set_desc(). */ void device_set_descf(device_t dev, const char *fmt, ...) { va_list ap; char *buf = NULL; va_start(ap, fmt); vasprintf(&buf, M_BUS, fmt, ap); va_end(ap); device_set_desc_internal(dev, buf, true); } /** * @brief Set the device's description * * The string pointed to by @c desc is copied. Use this function if * the device description is generated, (e.g. with sprintf()). */ void device_set_desc_copy(device_t dev, const char *desc) { char *buf; buf = strdup_flags(desc, M_BUS, M_WAITOK); device_set_desc_internal(dev, buf, true); } /** * @brief Set the device's flags */ void device_set_flags(device_t dev, uint32_t flags) { dev->devflags = flags; } /** * @brief Return the device's softc field * * The softc is allocated and zeroed when a driver is attached, based * on the size field of the driver. */ void * device_get_softc(device_t dev) { return (dev->softc); } /** * @brief Set the device's softc field * * Most drivers do not need to use this since the softc is allocated * automatically when the driver is attached. */ void device_set_softc(device_t dev, void *softc) { if (dev->softc && !(dev->flags & DF_EXTERNALSOFTC)) free(dev->softc, M_BUS_SC); dev->softc = softc; if (dev->softc) dev->flags |= DF_EXTERNALSOFTC; else dev->flags &= ~DF_EXTERNALSOFTC; } /** * @brief Free claimed softc * * Most drivers do not need to use this since the softc is freed * automatically when the driver is detached. */ void device_free_softc(void *softc) { free(softc, M_BUS_SC); } /** * @brief Claim softc * * This function can be used to let the driver free the automatically * allocated softc using "device_free_softc()". This function is * useful when the driver is refcounting the softc and the softc * cannot be freed when the "device_detach" method is called. */ void device_claim_softc(device_t dev) { if (dev->softc) dev->flags |= DF_EXTERNALSOFTC; else dev->flags &= ~DF_EXTERNALSOFTC; } /** * @brief Get the device's ivars field * * The ivars field is used by the parent device to store per-device * state (e.g. the physical location of the device or a list of * resources). */ void * device_get_ivars(device_t dev) { KASSERT(dev != NULL, ("device_get_ivars(NULL, ...)")); return (dev->ivars); } /** * @brief Set the device's ivars field */ void device_set_ivars(device_t dev, void * ivars) { KASSERT(dev != NULL, ("device_set_ivars(NULL, ...)")); dev->ivars = ivars; } /** * @brief Return the device's state */ device_state_t device_get_state(device_t dev) { return (dev->state); } /** * @brief Set the DF_ENABLED flag for the device */ void device_enable(device_t dev) { dev->flags |= DF_ENABLED; } /** * @brief Clear the DF_ENABLED flag for the device */ void device_disable(device_t dev) { dev->flags &= ~DF_ENABLED; } /** * @brief Increment the busy counter for the device */ void device_busy(device_t dev) { /* * Mark the device as busy, recursively up the tree if this busy count * goes 0->1. */ if (refcount_acquire(&dev->busy) == 0 && dev->parent != NULL) device_busy(dev->parent); } /** * @brief Decrement the busy counter for the device */ void device_unbusy(device_t dev) { /* * Mark the device as unbsy, recursively if this is the last busy count. */ if (refcount_release(&dev->busy) && dev->parent != NULL) device_unbusy(dev->parent); } /** * @brief Set the DF_QUIET flag for the device */ void device_quiet(device_t dev) { dev->flags |= DF_QUIET; } /** * @brief Set the DF_QUIET_CHILDREN flag for the device */ void device_quiet_children(device_t dev) { dev->flags |= DF_QUIET_CHILDREN; } /** * @brief Clear the DF_QUIET flag for the device */ void device_verbose(device_t dev) { dev->flags &= ~DF_QUIET; } ssize_t device_get_property(device_t dev, const char *prop, void *val, size_t sz, device_property_type_t type) { device_t bus = device_get_parent(dev); switch (type) { case DEVICE_PROP_ANY: case DEVICE_PROP_BUFFER: case DEVICE_PROP_HANDLE: /* Size checks done in implementation. */ break; case DEVICE_PROP_UINT32: if (sz % 4 != 0) return (-1); break; case DEVICE_PROP_UINT64: if (sz % 8 != 0) return (-1); break; default: return (-1); } return (BUS_GET_PROPERTY(bus, dev, prop, val, sz, type)); } bool device_has_property(device_t dev, const char *prop) { return (device_get_property(dev, prop, NULL, 0, DEVICE_PROP_ANY) >= 0); } /** * @brief Return non-zero if the DF_QUIET_CHIDLREN flag is set on the device */ int device_has_quiet_children(device_t dev) { return ((dev->flags & DF_QUIET_CHILDREN) != 0); } /** * @brief Return non-zero if the DF_QUIET flag is set on the device */ int device_is_quiet(device_t dev) { return ((dev->flags & DF_QUIET) != 0); } /** * @brief Return non-zero if the DF_ENABLED flag is set on the device */ int device_is_enabled(device_t dev) { return ((dev->flags & DF_ENABLED) != 0); } /** * @brief Return non-zero if the device was successfully probed */ int device_is_alive(device_t dev) { return (dev->state >= DS_ALIVE); } /** * @brief Return non-zero if the device currently has a driver * attached to it */ int device_is_attached(device_t dev) { return (dev->state >= DS_ATTACHED); } /** * @brief Return non-zero if the device is currently suspended. */ int device_is_suspended(device_t dev) { return ((dev->flags & DF_SUSPENDED) != 0); } /** * @brief Set the devclass of a device * @see devclass_add_device(). */ int device_set_devclass(device_t dev, const char *classname) { devclass_t dc; int error; if (!classname) { if (dev->devclass) devclass_delete_device(dev->devclass, dev); return (0); } if (dev->devclass) { printf("device_set_devclass: device class already set\n"); return (EINVAL); } dc = devclass_find_internal(classname, NULL, TRUE); if (!dc) return (ENOMEM); error = devclass_add_device(dc, dev); bus_data_generation_update(); return (error); } /** * @brief Set the devclass of a device and mark the devclass fixed. * @see device_set_devclass() */ int device_set_devclass_fixed(device_t dev, const char *classname) { int error; if (classname == NULL) return (EINVAL); error = device_set_devclass(dev, classname); if (error) return (error); dev->flags |= DF_FIXEDCLASS; return (0); } /** * @brief Query the device to determine if it's of a fixed devclass * @see device_set_devclass_fixed() */ bool device_is_devclass_fixed(device_t dev) { return ((dev->flags & DF_FIXEDCLASS) != 0); } /** * @brief Set the driver of a device * * @retval 0 success * @retval EBUSY the device already has a driver attached * @retval ENOMEM a memory allocation failure occurred */ int device_set_driver(device_t dev, driver_t *driver) { int domain; struct domainset *policy; if (dev->state >= DS_ATTACHED) return (EBUSY); if (dev->driver == driver) return (0); if (dev->softc && !(dev->flags & DF_EXTERNALSOFTC)) { free(dev->softc, M_BUS_SC); dev->softc = NULL; } device_set_desc(dev, NULL); kobj_delete((kobj_t) dev, NULL); dev->driver = driver; if (driver) { kobj_init((kobj_t) dev, (kobj_class_t) driver); if (!(dev->flags & DF_EXTERNALSOFTC) && driver->size > 0) { if (bus_get_domain(dev, &domain) == 0) policy = DOMAINSET_PREF(domain); else policy = DOMAINSET_RR(); dev->softc = malloc_domainset(driver->size, M_BUS_SC, policy, M_WAITOK | M_ZERO); } } else { kobj_init((kobj_t) dev, &null_class); } bus_data_generation_update(); return (0); } /** * @brief Probe a device, and return this status. * * This function is the core of the device autoconfiguration * system. Its purpose is to select a suitable driver for a device and * then call that driver to initialise the hardware appropriately. The * driver is selected by calling the DEVICE_PROBE() method of a set of * candidate drivers and then choosing the driver which returned the * best value. This driver is then attached to the device using * device_attach(). * * The set of suitable drivers is taken from the list of drivers in * the parent device's devclass. If the device was originally created * with a specific class name (see device_add_child()), only drivers * with that name are probed, otherwise all drivers in the devclass * are probed. If no drivers return successful probe values in the * parent devclass, the search continues in the parent of that * devclass (see devclass_get_parent()) if any. * * @param dev the device to initialise * * @retval 0 success * @retval ENXIO no driver was found * @retval ENOMEM memory allocation failure * @retval non-zero some other unix error code * @retval -1 Device already attached */ int device_probe(device_t dev) { int error; bus_topo_assert(); if (dev->state >= DS_ALIVE) return (-1); if (!(dev->flags & DF_ENABLED)) { if (bootverbose && device_get_name(dev) != NULL) { device_print_prettyname(dev); printf("not probed (disabled)\n"); } return (-1); } if ((error = device_probe_child(dev->parent, dev)) != 0) { if (bus_current_pass == BUS_PASS_DEFAULT && !(dev->flags & DF_DONENOMATCH)) { device_handle_nomatch(dev); } return (error); } return (0); } /** * @brief Probe a device and attach a driver if possible * * calls device_probe() and attaches if that was successful. */ int device_probe_and_attach(device_t dev) { int error; bus_topo_assert(); error = device_probe(dev); if (error == -1) return (0); else if (error != 0) return (error); return (device_attach(dev)); } /** * @brief Attach a device driver to a device * * This function is a wrapper around the DEVICE_ATTACH() driver * method. In addition to calling DEVICE_ATTACH(), it initialises the * device's sysctl tree, optionally prints a description of the device * and queues a notification event for user-based device management * services. * * Normally this function is only called internally from * device_probe_and_attach(). * * @param dev the device to initialise * * @retval 0 success * @retval ENXIO no driver was found * @retval ENOMEM memory allocation failure * @retval non-zero some other unix error code */ int device_attach(device_t dev) { uint64_t attachtime; uint16_t attachentropy; int error; if (resource_disabled(dev->driver->name, dev->unit)) { /* * Mostly detach the device, but leave it attached to * the devclass to reserve the name and unit. */ device_disable(dev); (void)device_set_driver(dev, NULL); dev->state = DS_NOTPRESENT; if (bootverbose) device_printf(dev, "disabled via hints entry\n"); return (ENXIO); } KASSERT(IS_DEFAULT_VNET(TD_TO_VNET(curthread)), ("device_attach: curthread is not in default vnet")); CURVNET_SET_QUIET(TD_TO_VNET(curthread)); device_sysctl_init(dev); if (!device_is_quiet(dev)) device_print_child(dev->parent, dev); attachtime = get_cyclecount(); dev->state = DS_ATTACHING; if ((error = DEVICE_ATTACH(dev)) != 0) { printf("device_attach: %s%d attach returned %d\n", dev->driver->name, dev->unit, error); BUS_CHILD_DETACHED(dev->parent, dev); if (disable_failed_devs) { /* * When the user has asked to disable failed devices, we * directly disable the device, but leave it in the * attaching state. It will not try to probe/attach the * device further. This leaves the device numbering * intact for other similar devices in the system. It * can be removed from this state with devctl. */ device_disable(dev); } else { /* * Otherwise, when attach fails, tear down the state * around that so we can retry when, for example, new * drivers are loaded. */ if (!(dev->flags & DF_FIXEDCLASS)) devclass_delete_device(dev->devclass, dev); (void)device_set_driver(dev, NULL); device_sysctl_fini(dev); KASSERT(dev->busy == 0, ("attach failed but busy")); dev->state = DS_NOTPRESENT; } CURVNET_RESTORE(); return (error); } CURVNET_RESTORE(); dev->flags |= DF_ATTACHED_ONCE; /* * We only need the low bits of this time, but ranges from tens to thousands * have been seen, so keep 2 bytes' worth. */ attachentropy = (uint16_t)(get_cyclecount() - attachtime); random_harvest_direct(&attachentropy, sizeof(attachentropy), RANDOM_ATTACH); device_sysctl_update(dev); dev->state = DS_ATTACHED; dev->flags &= ~DF_DONENOMATCH; EVENTHANDLER_DIRECT_INVOKE(device_attach, dev); return (0); } /** * @brief Detach a driver from a device * * This function is a wrapper around the DEVICE_DETACH() driver * method. If the call to DEVICE_DETACH() succeeds, it calls * BUS_CHILD_DETACHED() for the parent of @p dev, queues a * notification event for user-based device management services and * cleans up the device's sysctl tree. * * @param dev the device to un-initialise * * @retval 0 success * @retval ENXIO no driver was found * @retval ENOMEM memory allocation failure * @retval non-zero some other unix error code */ int device_detach(device_t dev) { int error; bus_topo_assert(); PDEBUG(("%s", DEVICENAME(dev))); if (dev->busy > 0) return (EBUSY); if (dev->state == DS_ATTACHING) { device_printf(dev, "device in attaching state! Deferring detach.\n"); return (EBUSY); } if (dev->state != DS_ATTACHED) return (0); EVENTHANDLER_DIRECT_INVOKE(device_detach, dev, EVHDEV_DETACH_BEGIN); if ((error = DEVICE_DETACH(dev)) != 0) { EVENTHANDLER_DIRECT_INVOKE(device_detach, dev, EVHDEV_DETACH_FAILED); return (error); } else { EVENTHANDLER_DIRECT_INVOKE(device_detach, dev, EVHDEV_DETACH_COMPLETE); } if (!device_is_quiet(dev)) device_printf(dev, "detached\n"); if (dev->parent) BUS_CHILD_DETACHED(dev->parent, dev); if (!(dev->flags & DF_FIXEDCLASS)) devclass_delete_device(dev->devclass, dev); device_verbose(dev); dev->state = DS_NOTPRESENT; (void)device_set_driver(dev, NULL); device_sysctl_fini(dev); return (0); } /** * @brief Tells a driver to quiesce itself. * * This function is a wrapper around the DEVICE_QUIESCE() driver * method. If the call to DEVICE_QUIESCE() succeeds. * * @param dev the device to quiesce * * @retval 0 success * @retval ENXIO no driver was found * @retval ENOMEM memory allocation failure * @retval non-zero some other unix error code */ int device_quiesce(device_t dev) { PDEBUG(("%s", DEVICENAME(dev))); if (dev->busy > 0) return (EBUSY); if (dev->state != DS_ATTACHED) return (0); return (DEVICE_QUIESCE(dev)); } /** * @brief Notify a device of system shutdown * * This function calls the DEVICE_SHUTDOWN() driver method if the * device currently has an attached driver. * * @returns the value returned by DEVICE_SHUTDOWN() */ int device_shutdown(device_t dev) { if (dev->state < DS_ATTACHED) return (0); return (DEVICE_SHUTDOWN(dev)); } /** * @brief Set the unit number of a device * * This function can be used to override the unit number used for a * device (e.g. to wire a device to a pre-configured unit number). */ int device_set_unit(device_t dev, int unit) { devclass_t dc; int err; if (unit == dev->unit) return (0); dc = device_get_devclass(dev); if (unit < dc->maxunit && dc->devices[unit]) return (EBUSY); err = devclass_delete_device(dc, dev); if (err) return (err); dev->unit = unit; err = devclass_add_device(dc, dev); if (err) return (err); bus_data_generation_update(); return (0); } /*======================================*/ /* * Some useful method implementations to make life easier for bus drivers. */ /** * @brief Initialize a resource mapping request * * This is the internal implementation of the public API * resource_init_map_request. Callers may be using a different layout * of struct resource_map_request than the kernel, so callers pass in * the size of the structure they are using to identify the structure * layout. */ void resource_init_map_request_impl(struct resource_map_request *args, size_t sz) { bzero(args, sz); args->size = sz; args->memattr = VM_MEMATTR_DEVICE; } /** * @brief Validate a resource mapping request * * Translate a device driver's mapping request (@p in) to a struct * resource_map_request using the current structure layout (@p out). * In addition, validate the offset and length from the mapping * request against the bounds of the resource @p r. If the offset or * length are invalid, fail with EINVAL. If the offset and length are * valid, the absolute starting address of the requested mapping is * returned in @p startp and the length of the requested mapping is * returned in @p lengthp. */ int resource_validate_map_request(struct resource *r, struct resource_map_request *in, struct resource_map_request *out, rman_res_t *startp, rman_res_t *lengthp) { rman_res_t end, length, start; /* * This assumes that any callers of this function are compiled * into the kernel and use the same version of the structure * as this file. */ MPASS(out->size == sizeof(struct resource_map_request)); if (in != NULL) bcopy(in, out, imin(in->size, out->size)); start = rman_get_start(r) + out->offset; if (out->length == 0) length = rman_get_size(r); else length = out->length; end = start + length - 1; if (start > rman_get_end(r) || start < rman_get_start(r)) return (EINVAL); if (end > rman_get_end(r) || end < start) return (EINVAL); *lengthp = length; *startp = start; return (0); } /** * @brief Initialise a resource list. * * @param rl the resource list to initialise */ void resource_list_init(struct resource_list *rl) { STAILQ_INIT(rl); } /** * @brief Reclaim memory used by a resource list. * * This function frees the memory for all resource entries on the list * (if any). * * @param rl the resource list to free */ void resource_list_free(struct resource_list *rl) { struct resource_list_entry *rle; while ((rle = STAILQ_FIRST(rl)) != NULL) { if (rle->res) panic("resource_list_free: resource entry is busy"); STAILQ_REMOVE_HEAD(rl, link); free(rle, M_BUS); } } /** * @brief Add a resource entry. * * This function adds a resource entry using the given @p type, @p * start, @p end and @p count values. A rid value is chosen by * searching sequentially for the first unused rid starting at zero. * * @param rl the resource list to edit * @param type the resource entry type (e.g. SYS_RES_MEMORY) * @param start the start address of the resource * @param end the end address of the resource * @param count XXX end-start+1 */ int resource_list_add_next(struct resource_list *rl, int type, rman_res_t start, rman_res_t end, rman_res_t count) { int rid; rid = 0; while (resource_list_find(rl, type, rid) != NULL) rid++; resource_list_add(rl, type, rid, start, end, count); return (rid); } /** * @brief Add or modify a resource entry. * * If an existing entry exists with the same type and rid, it will be * modified using the given values of @p start, @p end and @p * count. If no entry exists, a new one will be created using the * given values. The resource list entry that matches is then returned. * * @param rl the resource list to edit * @param type the resource entry type (e.g. SYS_RES_MEMORY) * @param rid the resource identifier * @param start the start address of the resource * @param end the end address of the resource * @param count XXX end-start+1 */ struct resource_list_entry * resource_list_add(struct resource_list *rl, int type, int rid, rman_res_t start, rman_res_t end, rman_res_t count) { struct resource_list_entry *rle; rle = resource_list_find(rl, type, rid); if (!rle) { rle = malloc(sizeof(struct resource_list_entry), M_BUS, M_WAITOK); STAILQ_INSERT_TAIL(rl, rle, link); rle->type = type; rle->rid = rid; rle->res = NULL; rle->flags = 0; } if (rle->res) panic("resource_list_add: resource entry is busy"); rle->start = start; rle->end = end; rle->count = count; return (rle); } /** * @brief Determine if a resource entry is busy. * * Returns true if a resource entry is busy meaning that it has an * associated resource that is not an unallocated "reserved" resource. * * @param rl the resource list to search * @param type the resource entry type (e.g. SYS_RES_MEMORY) * @param rid the resource identifier * * @returns Non-zero if the entry is busy, zero otherwise. */ int resource_list_busy(struct resource_list *rl, int type, int rid) { struct resource_list_entry *rle; rle = resource_list_find(rl, type, rid); if (rle == NULL || rle->res == NULL) return (0); if ((rle->flags & (RLE_RESERVED | RLE_ALLOCATED)) == RLE_RESERVED) { KASSERT(!(rman_get_flags(rle->res) & RF_ACTIVE), ("reserved resource is active")); return (0); } return (1); } /** * @brief Determine if a resource entry is reserved. * * Returns true if a resource entry is reserved meaning that it has an * associated "reserved" resource. The resource can either be * allocated or unallocated. * * @param rl the resource list to search * @param type the resource entry type (e.g. SYS_RES_MEMORY) * @param rid the resource identifier * * @returns Non-zero if the entry is reserved, zero otherwise. */ int resource_list_reserved(struct resource_list *rl, int type, int rid) { struct resource_list_entry *rle; rle = resource_list_find(rl, type, rid); if (rle != NULL && rle->flags & RLE_RESERVED) return (1); return (0); } /** * @brief Find a resource entry by type and rid. * * @param rl the resource list to search * @param type the resource entry type (e.g. SYS_RES_MEMORY) * @param rid the resource identifier * * @returns the resource entry pointer or NULL if there is no such * entry. */ struct resource_list_entry * resource_list_find(struct resource_list *rl, int type, int rid) { struct resource_list_entry *rle; STAILQ_FOREACH(rle, rl, link) { if (rle->type == type && rle->rid == rid) return (rle); } return (NULL); } /** * @brief Delete a resource entry. * * @param rl the resource list to edit * @param type the resource entry type (e.g. SYS_RES_MEMORY) * @param rid the resource identifier */ void resource_list_delete(struct resource_list *rl, int type, int rid) { struct resource_list_entry *rle = resource_list_find(rl, type, rid); if (rle) { if (rle->res != NULL) panic("resource_list_delete: resource has not been released"); STAILQ_REMOVE(rl, rle, resource_list_entry, link); free(rle, M_BUS); } } /** * @brief Allocate a reserved resource * * This can be used by buses to force the allocation of resources * that are always active in the system even if they are not allocated * by a driver (e.g. PCI BARs). This function is usually called when * adding a new child to the bus. The resource is allocated from the * parent bus when it is reserved. The resource list entry is marked * with RLE_RESERVED to note that it is a reserved resource. * * Subsequent attempts to allocate the resource with * resource_list_alloc() will succeed the first time and will set * RLE_ALLOCATED to note that it has been allocated. When a reserved * resource that has been allocated is released with * resource_list_release() the resource RLE_ALLOCATED is cleared, but * the actual resource remains allocated. The resource can be released to * the parent bus by calling resource_list_unreserve(). * * @param rl the resource list to allocate from * @param bus the parent device of @p child * @param child the device for which the resource is being reserved * @param type the type of resource to allocate * @param rid a pointer to the resource identifier * @param start hint at the start of the resource range - pass * @c 0 for any start address * @param end hint at the end of the resource range - pass * @c ~0 for any end address * @param count hint at the size of range required - pass @c 1 * for any size * @param flags any extra flags to control the resource * allocation - see @c RF_XXX flags in * for details * * @returns the resource which was allocated or @c NULL if no * resource could be allocated */ struct resource * resource_list_reserve(struct resource_list *rl, device_t bus, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct resource_list_entry *rle = NULL; int passthrough = (device_get_parent(child) != bus); struct resource *r; if (passthrough) panic( "resource_list_reserve() should only be called for direct children"); if (flags & RF_ACTIVE) panic( "resource_list_reserve() should only reserve inactive resources"); r = resource_list_alloc(rl, bus, child, type, rid, start, end, count, flags); if (r != NULL) { rle = resource_list_find(rl, type, *rid); rle->flags |= RLE_RESERVED; } return (r); } /** * @brief Helper function for implementing BUS_ALLOC_RESOURCE() * * Implement BUS_ALLOC_RESOURCE() by looking up a resource from the list * and passing the allocation up to the parent of @p bus. This assumes * that the first entry of @c device_get_ivars(child) is a struct * resource_list. This also handles 'passthrough' allocations where a * child is a remote descendant of bus by passing the allocation up to * the parent of bus. * * Typically, a bus driver would store a list of child resources * somewhere in the child device's ivars (see device_get_ivars()) and * its implementation of BUS_ALLOC_RESOURCE() would find that list and * then call resource_list_alloc() to perform the allocation. * * @param rl the resource list to allocate from * @param bus the parent device of @p child * @param child the device which is requesting an allocation * @param type the type of resource to allocate * @param rid a pointer to the resource identifier * @param start hint at the start of the resource range - pass * @c 0 for any start address * @param end hint at the end of the resource range - pass * @c ~0 for any end address * @param count hint at the size of range required - pass @c 1 * for any size * @param flags any extra flags to control the resource * allocation - see @c RF_XXX flags in * for details * * @returns the resource which was allocated or @c NULL if no * resource could be allocated */ struct resource * resource_list_alloc(struct resource_list *rl, device_t bus, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct resource_list_entry *rle = NULL; int passthrough = (device_get_parent(child) != bus); int isdefault = RMAN_IS_DEFAULT_RANGE(start, end); if (passthrough) { return (BUS_ALLOC_RESOURCE(device_get_parent(bus), child, type, rid, start, end, count, flags)); } rle = resource_list_find(rl, type, *rid); if (!rle) return (NULL); /* no resource of that type/rid */ if (rle->res) { if (rle->flags & RLE_RESERVED) { if (rle->flags & RLE_ALLOCATED) return (NULL); if ((flags & RF_ACTIVE) && bus_activate_resource(child, type, *rid, rle->res) != 0) return (NULL); rle->flags |= RLE_ALLOCATED; return (rle->res); } device_printf(bus, "resource entry %#x type %d for child %s is busy\n", *rid, type, device_get_nameunit(child)); return (NULL); } if (isdefault) { start = rle->start; count = ulmax(count, rle->count); end = ulmax(rle->end, start + count - 1); } rle->res = BUS_ALLOC_RESOURCE(device_get_parent(bus), child, type, rid, start, end, count, flags); /* * Record the new range. */ if (rle->res) { rle->start = rman_get_start(rle->res); rle->end = rman_get_end(rle->res); rle->count = count; } return (rle->res); } /** * @brief Helper function for implementing BUS_RELEASE_RESOURCE() * * Implement BUS_RELEASE_RESOURCE() using a resource list. Normally * used with resource_list_alloc(). * * @param rl the resource list which was allocated from * @param bus the parent device of @p child * @param child the device which is requesting a release * @param res the resource to release * * @retval 0 success * @retval non-zero a standard unix error code indicating what * error condition prevented the operation */ int resource_list_release(struct resource_list *rl, device_t bus, device_t child, struct resource *res) { struct resource_list_entry *rle = NULL; int passthrough = (device_get_parent(child) != bus); int error; if (passthrough) { return (BUS_RELEASE_RESOURCE(device_get_parent(bus), child, res)); } rle = resource_list_find(rl, rman_get_type(res), rman_get_rid(res)); if (!rle) panic("resource_list_release: can't find resource"); if (!rle->res) panic("resource_list_release: resource entry is not busy"); if (rle->flags & RLE_RESERVED) { if (rle->flags & RLE_ALLOCATED) { if (rman_get_flags(res) & RF_ACTIVE) { error = bus_deactivate_resource(child, res); if (error) return (error); } rle->flags &= ~RLE_ALLOCATED; return (0); } return (EINVAL); } error = BUS_RELEASE_RESOURCE(device_get_parent(bus), child, res); if (error) return (error); rle->res = NULL; return (0); } /** * @brief Release all active resources of a given type * * Release all active resources of a specified type. This is intended * to be used to cleanup resources leaked by a driver after detach or * a failed attach. * * @param rl the resource list which was allocated from * @param bus the parent device of @p child * @param child the device whose active resources are being released * @param type the type of resources to release * * @retval 0 success * @retval EBUSY at least one resource was active */ int resource_list_release_active(struct resource_list *rl, device_t bus, device_t child, int type) { struct resource_list_entry *rle; int error, retval; retval = 0; STAILQ_FOREACH(rle, rl, link) { if (rle->type != type) continue; if (rle->res == NULL) continue; if ((rle->flags & (RLE_RESERVED | RLE_ALLOCATED)) == RLE_RESERVED) continue; retval = EBUSY; error = resource_list_release(rl, bus, child, rle->res); if (error != 0) device_printf(bus, "Failed to release active resource: %d\n", error); } return (retval); } /** * @brief Fully release a reserved resource * * Fully releases a resource reserved via resource_list_reserve(). * * @param rl the resource list which was allocated from * @param bus the parent device of @p child * @param child the device whose reserved resource is being released * @param type the type of resource to release * @param rid the resource identifier * @param res the resource to release * * @retval 0 success * @retval non-zero a standard unix error code indicating what * error condition prevented the operation */ int resource_list_unreserve(struct resource_list *rl, device_t bus, device_t child, int type, int rid) { struct resource_list_entry *rle = NULL; int passthrough = (device_get_parent(child) != bus); if (passthrough) panic( "resource_list_unreserve() should only be called for direct children"); rle = resource_list_find(rl, type, rid); if (!rle) panic("resource_list_unreserve: can't find resource"); if (!(rle->flags & RLE_RESERVED)) return (EINVAL); if (rle->flags & RLE_ALLOCATED) return (EBUSY); rle->flags &= ~RLE_RESERVED; return (resource_list_release(rl, bus, child, rle->res)); } /** * @brief Print a description of resources in a resource list * * Print all resources of a specified type, for use in BUS_PRINT_CHILD(). * The name is printed if at least one resource of the given type is available. * The format is used to print resource start and end. * * @param rl the resource list to print * @param name the name of @p type, e.g. @c "memory" * @param type type type of resource entry to print * @param format printf(9) format string to print resource * start and end values * * @returns the number of characters printed */ int resource_list_print_type(struct resource_list *rl, const char *name, int type, const char *format) { struct resource_list_entry *rle; int printed, retval; printed = 0; retval = 0; /* Yes, this is kinda cheating */ STAILQ_FOREACH(rle, rl, link) { if (rle->type == type) { if (printed == 0) retval += printf(" %s ", name); else retval += printf(","); printed++; retval += printf(format, rle->start); if (rle->count > 1) { retval += printf("-"); retval += printf(format, rle->start + rle->count - 1); } } } return (retval); } /** * @brief Releases all the resources in a list. * * @param rl The resource list to purge. * * @returns nothing */ void resource_list_purge(struct resource_list *rl) { struct resource_list_entry *rle; while ((rle = STAILQ_FIRST(rl)) != NULL) { if (rle->res) bus_release_resource(rman_get_device(rle->res), rle->type, rle->rid, rle->res); STAILQ_REMOVE_HEAD(rl, link); free(rle, M_BUS); } } device_t bus_generic_add_child(device_t dev, u_int order, const char *name, int unit) { return (device_add_child_ordered(dev, order, name, unit)); } /** * @brief Helper function for implementing DEVICE_PROBE() * * This function can be used to help implement the DEVICE_PROBE() for * a bus (i.e. a device which has other devices attached to it). It * calls the DEVICE_IDENTIFY() method of each driver in the device's * devclass. */ int bus_generic_probe(device_t dev) { bus_identify_children(dev); return (0); } /** * @brief Ask drivers to add child devices of the given device. * * This function allows drivers for child devices of a bus to identify * child devices and add them as children of the given device. NB: * The driver for @param dev must implement the BUS_ADD_CHILD method. * * @param dev the parent device */ void bus_identify_children(device_t dev) { devclass_t dc = dev->devclass; driverlink_t dl; TAILQ_FOREACH(dl, &dc->drivers, link) { /* * If this driver's pass is too high, then ignore it. * For most drivers in the default pass, this will * never be true. For early-pass drivers they will * only call the identify routines of eligible drivers * when this routine is called. Drivers for later * passes should have their identify routines called * on early-pass buses during BUS_NEW_PASS(). */ if (dl->pass > bus_current_pass) continue; DEVICE_IDENTIFY(dl->driver, dev); } } /** * @brief Helper function for implementing DEVICE_ATTACH() * * This function can be used to help implement the DEVICE_ATTACH() for * a bus. It calls device_probe_and_attach() for each of the device's * children. */ int bus_generic_attach(device_t dev) { bus_attach_children(dev); return (0); } /** * @brief Probe and attach all children of the given device * * This function attempts to attach a device driver to each unattached * child of the given device using device_probe_and_attach(). If an * individual child fails to attach this function continues attaching * other children. * * @param dev the parent device */ void bus_attach_children(device_t dev) { device_t child; TAILQ_FOREACH(child, &dev->children, link) { device_probe_and_attach(child); } } /** * @brief Helper function for delaying attaching children * * Many buses can't run transactions on the bus which children need to probe and * attach until after interrupts and/or timers are running. This function * delays their attach until interrupts and timers are enabled. */ void bus_delayed_attach_children(device_t dev) { /* Probe and attach the bus children when interrupts are available */ config_intrhook_oneshot((ich_func_t)bus_attach_children, dev); } /** * @brief Helper function for implementing DEVICE_DETACH() * * This function can be used to help implement the DEVICE_DETACH() for * a bus. It detaches and deletes all children. If an individual * child fails to detach, this function stops and returns an error. * * @param dev the parent device * * @retval 0 success * @retval non-zero a device would not detach */ int bus_generic_detach(device_t dev) { int error; error = bus_detach_children(dev); if (error != 0) return (error); return (device_delete_children(dev)); } /** * @brief Detach drivers from all children of a device * * This function attempts to detach a device driver from each attached * child of the given device using device_detach(). If an individual * child fails to detach this function stops and returns an error. * NB: Children that were successfully detached are not re-attached if * an error occurs. * * @param dev the parent device * * @retval 0 success * @retval non-zero a device would not detach */ int bus_detach_children(device_t dev) { device_t child; int error; /* * Detach children in the reverse order. * See bus_generic_suspend for details. */ TAILQ_FOREACH_REVERSE(child, &dev->children, device_list, link) { if ((error = device_detach(child)) != 0) return (error); } return (0); } /** * @brief Helper function for implementing DEVICE_SHUTDOWN() * * This function can be used to help implement the DEVICE_SHUTDOWN() * for a bus. It calls device_shutdown() for each of the device's * children. */ int bus_generic_shutdown(device_t dev) { device_t child; /* * Shut down children in the reverse order. * See bus_generic_suspend for details. */ TAILQ_FOREACH_REVERSE(child, &dev->children, device_list, link) { device_shutdown(child); } return (0); } /** * @brief Default function for suspending a child device. * * This function is to be used by a bus's DEVICE_SUSPEND_CHILD(). */ int bus_generic_suspend_child(device_t dev, device_t child) { int error; error = DEVICE_SUSPEND(child); if (error == 0) { child->flags |= DF_SUSPENDED; } else { printf("DEVICE_SUSPEND(%s) failed: %d\n", device_get_nameunit(child), error); } return (error); } /** * @brief Default function for resuming a child device. * * This function is to be used by a bus's DEVICE_RESUME_CHILD(). */ int bus_generic_resume_child(device_t dev, device_t child) { DEVICE_RESUME(child); child->flags &= ~DF_SUSPENDED; return (0); } /** * @brief Helper function for implementing DEVICE_SUSPEND() * * This function can be used to help implement the DEVICE_SUSPEND() * for a bus. It calls DEVICE_SUSPEND() for each of the device's * children. If any call to DEVICE_SUSPEND() fails, the suspend * operation is aborted and any devices which were suspended are * resumed immediately by calling their DEVICE_RESUME() methods. */ int bus_generic_suspend(device_t dev) { int error; device_t child; /* * Suspend children in the reverse order. * For most buses all children are equal, so the order does not matter. * Other buses, such as acpi, carefully order their child devices to * express implicit dependencies between them. For such buses it is * safer to bring down devices in the reverse order. */ TAILQ_FOREACH_REVERSE(child, &dev->children, device_list, link) { error = BUS_SUSPEND_CHILD(dev, child); if (error != 0) { child = TAILQ_NEXT(child, link); if (child != NULL) { TAILQ_FOREACH_FROM(child, &dev->children, link) BUS_RESUME_CHILD(dev, child); } return (error); } } return (0); } /** * @brief Helper function for implementing DEVICE_RESUME() * * This function can be used to help implement the DEVICE_RESUME() for * a bus. It calls DEVICE_RESUME() on each of the device's children. */ int bus_generic_resume(device_t dev) { device_t child; TAILQ_FOREACH(child, &dev->children, link) { BUS_RESUME_CHILD(dev, child); /* if resume fails, there's nothing we can usefully do... */ } return (0); } /** * @brief Helper function for implementing BUS_RESET_POST * * Bus can use this function to implement common operations of * re-attaching or resuming the children after the bus itself was * reset, and after restoring bus-unique state of children. * * @param dev The bus * #param flags DEVF_RESET_* */ int bus_helper_reset_post(device_t dev, int flags) { device_t child; int error, error1; error = 0; TAILQ_FOREACH(child, &dev->children,link) { BUS_RESET_POST(dev, child); error1 = (flags & DEVF_RESET_DETACH) != 0 ? device_probe_and_attach(child) : BUS_RESUME_CHILD(dev, child); if (error == 0 && error1 != 0) error = error1; } return (error); } static void bus_helper_reset_prepare_rollback(device_t dev, device_t child, int flags) { child = TAILQ_NEXT(child, link); if (child == NULL) return; TAILQ_FOREACH_FROM(child, &dev->children,link) { BUS_RESET_POST(dev, child); if ((flags & DEVF_RESET_DETACH) != 0) device_probe_and_attach(child); else BUS_RESUME_CHILD(dev, child); } } /** * @brief Helper function for implementing BUS_RESET_PREPARE * * Bus can use this function to implement common operations of * detaching or suspending the children before the bus itself is * reset, and then save bus-unique state of children that must * persists around reset. * * @param dev The bus * #param flags DEVF_RESET_* */ int bus_helper_reset_prepare(device_t dev, int flags) { device_t child; int error; if (dev->state != DS_ATTACHED) return (EBUSY); TAILQ_FOREACH_REVERSE(child, &dev->children, device_list, link) { if ((flags & DEVF_RESET_DETACH) != 0) { error = device_get_state(child) == DS_ATTACHED ? device_detach(child) : 0; } else { error = BUS_SUSPEND_CHILD(dev, child); } if (error == 0) { error = BUS_RESET_PREPARE(dev, child); if (error != 0) { if ((flags & DEVF_RESET_DETACH) != 0) device_probe_and_attach(child); else BUS_RESUME_CHILD(dev, child); } } if (error != 0) { bus_helper_reset_prepare_rollback(dev, child, flags); return (error); } } return (0); } /** * @brief Helper function for implementing BUS_PRINT_CHILD(). * * This function prints the first part of the ascii representation of * @p child, including its name, unit and description (if any - see * device_set_desc()). * * @returns the number of characters printed */ int bus_print_child_header(device_t dev, device_t child) { int retval = 0; if (device_get_desc(child)) { retval += device_printf(child, "<%s>", device_get_desc(child)); } else { retval += printf("%s", device_get_nameunit(child)); } return (retval); } /** * @brief Helper function for implementing BUS_PRINT_CHILD(). * * This function prints the last part of the ascii representation of * @p child, which consists of the string @c " on " followed by the * name and unit of the @p dev. * * @returns the number of characters printed */ int bus_print_child_footer(device_t dev, device_t child) { return (printf(" on %s\n", device_get_nameunit(dev))); } /** * @brief Helper function for implementing BUS_PRINT_CHILD(). * * This function prints out the VM domain for the given device. * * @returns the number of characters printed */ int bus_print_child_domain(device_t dev, device_t child) { int domain; /* No domain? Don't print anything */ if (BUS_GET_DOMAIN(dev, child, &domain) != 0) return (0); return (printf(" numa-domain %d", domain)); } /** * @brief Helper function for implementing BUS_PRINT_CHILD(). * * This function simply calls bus_print_child_header() followed by * bus_print_child_footer(). * * @returns the number of characters printed */ int bus_generic_print_child(device_t dev, device_t child) { int retval = 0; retval += bus_print_child_header(dev, child); retval += bus_print_child_domain(dev, child); retval += bus_print_child_footer(dev, child); return (retval); } /** * @brief Stub function for implementing BUS_READ_IVAR(). * * @returns ENOENT */ int bus_generic_read_ivar(device_t dev, device_t child, int index, uintptr_t * result) { return (ENOENT); } /** * @brief Stub function for implementing BUS_WRITE_IVAR(). * * @returns ENOENT */ int bus_generic_write_ivar(device_t dev, device_t child, int index, uintptr_t value) { return (ENOENT); } /** * @brief Helper function for implementing BUS_GET_PROPERTY(). * * This simply calls the BUS_GET_PROPERTY of the parent of dev, * until a non-default implementation is found. */ ssize_t bus_generic_get_property(device_t dev, device_t child, const char *propname, void *propvalue, size_t size, device_property_type_t type) { if (device_get_parent(dev) != NULL) return (BUS_GET_PROPERTY(device_get_parent(dev), child, propname, propvalue, size, type)); return (-1); } /** * @brief Helper function for implementing BUS_DRIVER_ADDED(). * * This implementation of BUS_DRIVER_ADDED() simply calls the driver's * DEVICE_IDENTIFY() method to allow it to add new children to the bus * and then calls device_probe_and_attach() for each unattached child. */ void bus_generic_driver_added(device_t dev, driver_t *driver) { device_t child; DEVICE_IDENTIFY(driver, dev); TAILQ_FOREACH(child, &dev->children, link) { if (child->state == DS_NOTPRESENT) device_probe_and_attach(child); } } /** * @brief Helper function for implementing BUS_NEW_PASS(). * * This implementing of BUS_NEW_PASS() first calls the identify * routines for any drivers that probe at the current pass. Then it * walks the list of devices for this bus. If a device is already * attached, then it calls BUS_NEW_PASS() on that device. If the * device is not already attached, it attempts to attach a driver to * it. */ void bus_generic_new_pass(device_t dev) { driverlink_t dl; devclass_t dc; device_t child; dc = dev->devclass; TAILQ_FOREACH(dl, &dc->drivers, link) { if (dl->pass == bus_current_pass) DEVICE_IDENTIFY(dl->driver, dev); } TAILQ_FOREACH(child, &dev->children, link) { if (child->state >= DS_ATTACHED) BUS_NEW_PASS(child); else if (child->state == DS_NOTPRESENT) device_probe_and_attach(child); } } /** * @brief Helper function for implementing BUS_SETUP_INTR(). * * This simple implementation of BUS_SETUP_INTR() simply calls the * BUS_SETUP_INTR() method of the parent of @p dev. */ int bus_generic_setup_intr(device_t dev, device_t child, struct resource *irq, int flags, driver_filter_t *filter, driver_intr_t *intr, void *arg, void **cookiep) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent) return (BUS_SETUP_INTR(dev->parent, child, irq, flags, filter, intr, arg, cookiep)); return (EINVAL); } /** * @brief Helper function for implementing BUS_TEARDOWN_INTR(). * * This simple implementation of BUS_TEARDOWN_INTR() simply calls the * BUS_TEARDOWN_INTR() method of the parent of @p dev. */ int bus_generic_teardown_intr(device_t dev, device_t child, struct resource *irq, void *cookie) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent) return (BUS_TEARDOWN_INTR(dev->parent, child, irq, cookie)); return (EINVAL); } /** * @brief Helper function for implementing BUS_SUSPEND_INTR(). * * This simple implementation of BUS_SUSPEND_INTR() simply calls the * BUS_SUSPEND_INTR() method of the parent of @p dev. */ int bus_generic_suspend_intr(device_t dev, device_t child, struct resource *irq) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent) return (BUS_SUSPEND_INTR(dev->parent, child, irq)); return (EINVAL); } /** * @brief Helper function for implementing BUS_RESUME_INTR(). * * This simple implementation of BUS_RESUME_INTR() simply calls the * BUS_RESUME_INTR() method of the parent of @p dev. */ int bus_generic_resume_intr(device_t dev, device_t child, struct resource *irq) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent) return (BUS_RESUME_INTR(dev->parent, child, irq)); return (EINVAL); } /** * @brief Helper function for implementing BUS_ADJUST_RESOURCE(). * * This simple implementation of BUS_ADJUST_RESOURCE() simply calls the * BUS_ADJUST_RESOURCE() method of the parent of @p dev. */ int bus_generic_adjust_resource(device_t dev, device_t child, struct resource *r, rman_res_t start, rman_res_t end) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent) return (BUS_ADJUST_RESOURCE(dev->parent, child, r, start, end)); return (EINVAL); } /* * @brief Helper function for implementing BUS_TRANSLATE_RESOURCE(). * * This simple implementation of BUS_TRANSLATE_RESOURCE() simply calls the * BUS_TRANSLATE_RESOURCE() method of the parent of @p dev. If there is no * parent, no translation happens. */ int bus_generic_translate_resource(device_t dev, int type, rman_res_t start, rman_res_t *newstart) { if (dev->parent) return (BUS_TRANSLATE_RESOURCE(dev->parent, type, start, newstart)); *newstart = start; return (0); } /** * @brief Helper function for implementing BUS_ALLOC_RESOURCE(). * * This simple implementation of BUS_ALLOC_RESOURCE() simply calls the * BUS_ALLOC_RESOURCE() method of the parent of @p dev. */ struct resource * bus_generic_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent) return (BUS_ALLOC_RESOURCE(dev->parent, child, type, rid, start, end, count, flags)); return (NULL); } /** * @brief Helper function for implementing BUS_RELEASE_RESOURCE(). * * This simple implementation of BUS_RELEASE_RESOURCE() simply calls the * BUS_RELEASE_RESOURCE() method of the parent of @p dev. */ int bus_generic_release_resource(device_t dev, device_t child, struct resource *r) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent) return (BUS_RELEASE_RESOURCE(dev->parent, child, r)); return (EINVAL); } /** * @brief Helper function for implementing BUS_ACTIVATE_RESOURCE(). * * This simple implementation of BUS_ACTIVATE_RESOURCE() simply calls the * BUS_ACTIVATE_RESOURCE() method of the parent of @p dev. */ int bus_generic_activate_resource(device_t dev, device_t child, struct resource *r) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent) return (BUS_ACTIVATE_RESOURCE(dev->parent, child, r)); return (EINVAL); } /** * @brief Helper function for implementing BUS_DEACTIVATE_RESOURCE(). * * This simple implementation of BUS_DEACTIVATE_RESOURCE() simply calls the * BUS_DEACTIVATE_RESOURCE() method of the parent of @p dev. */ int bus_generic_deactivate_resource(device_t dev, device_t child, struct resource *r) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent) return (BUS_DEACTIVATE_RESOURCE(dev->parent, child, r)); return (EINVAL); } /** * @brief Helper function for implementing BUS_MAP_RESOURCE(). * * This simple implementation of BUS_MAP_RESOURCE() simply calls the * BUS_MAP_RESOURCE() method of the parent of @p dev. */ int bus_generic_map_resource(device_t dev, device_t child, struct resource *r, struct resource_map_request *args, struct resource_map *map) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent) return (BUS_MAP_RESOURCE(dev->parent, child, r, args, map)); return (EINVAL); } /** * @brief Helper function for implementing BUS_UNMAP_RESOURCE(). * * This simple implementation of BUS_UNMAP_RESOURCE() simply calls the * BUS_UNMAP_RESOURCE() method of the parent of @p dev. */ int bus_generic_unmap_resource(device_t dev, device_t child, struct resource *r, struct resource_map *map) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent) return (BUS_UNMAP_RESOURCE(dev->parent, child, r, map)); return (EINVAL); } /** * @brief Helper function for implementing BUS_BIND_INTR(). * * This simple implementation of BUS_BIND_INTR() simply calls the * BUS_BIND_INTR() method of the parent of @p dev. */ int bus_generic_bind_intr(device_t dev, device_t child, struct resource *irq, int cpu) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent) return (BUS_BIND_INTR(dev->parent, child, irq, cpu)); return (EINVAL); } /** * @brief Helper function for implementing BUS_CONFIG_INTR(). * * This simple implementation of BUS_CONFIG_INTR() simply calls the * BUS_CONFIG_INTR() method of the parent of @p dev. */ int bus_generic_config_intr(device_t dev, int irq, enum intr_trigger trig, enum intr_polarity pol) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent) return (BUS_CONFIG_INTR(dev->parent, irq, trig, pol)); return (EINVAL); } /** * @brief Helper function for implementing BUS_DESCRIBE_INTR(). * * This simple implementation of BUS_DESCRIBE_INTR() simply calls the * BUS_DESCRIBE_INTR() method of the parent of @p dev. */ int bus_generic_describe_intr(device_t dev, device_t child, struct resource *irq, void *cookie, const char *descr) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent) return (BUS_DESCRIBE_INTR(dev->parent, child, irq, cookie, descr)); return (EINVAL); } /** * @brief Helper function for implementing BUS_GET_CPUS(). * * This simple implementation of BUS_GET_CPUS() simply calls the * BUS_GET_CPUS() method of the parent of @p dev. */ int bus_generic_get_cpus(device_t dev, device_t child, enum cpu_sets op, size_t setsize, cpuset_t *cpuset) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent != NULL) return (BUS_GET_CPUS(dev->parent, child, op, setsize, cpuset)); return (EINVAL); } /** * @brief Helper function for implementing BUS_GET_DMA_TAG(). * * This simple implementation of BUS_GET_DMA_TAG() simply calls the * BUS_GET_DMA_TAG() method of the parent of @p dev. */ bus_dma_tag_t bus_generic_get_dma_tag(device_t dev, device_t child) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent != NULL) return (BUS_GET_DMA_TAG(dev->parent, child)); return (NULL); } /** * @brief Helper function for implementing BUS_GET_BUS_TAG(). * * This simple implementation of BUS_GET_BUS_TAG() simply calls the * BUS_GET_BUS_TAG() method of the parent of @p dev. */ bus_space_tag_t bus_generic_get_bus_tag(device_t dev, device_t child) { /* Propagate up the bus hierarchy until someone handles it. */ if (dev->parent != NULL) return (BUS_GET_BUS_TAG(dev->parent, child)); return ((bus_space_tag_t)0); } /** * @brief Helper function for implementing BUS_GET_RESOURCE(). * * This implementation of BUS_GET_RESOURCE() uses the * resource_list_find() function to do most of the work. It calls * BUS_GET_RESOURCE_LIST() to find a suitable resource list to * search. */ int bus_generic_rl_get_resource(device_t dev, device_t child, int type, int rid, rman_res_t *startp, rman_res_t *countp) { struct resource_list * rl = NULL; struct resource_list_entry * rle = NULL; rl = BUS_GET_RESOURCE_LIST(dev, child); if (!rl) return (EINVAL); rle = resource_list_find(rl, type, rid); if (!rle) return (ENOENT); if (startp) *startp = rle->start; if (countp) *countp = rle->count; return (0); } /** * @brief Helper function for implementing BUS_SET_RESOURCE(). * * This implementation of BUS_SET_RESOURCE() uses the * resource_list_add() function to do most of the work. It calls * BUS_GET_RESOURCE_LIST() to find a suitable resource list to * edit. */ int bus_generic_rl_set_resource(device_t dev, device_t child, int type, int rid, rman_res_t start, rman_res_t count) { struct resource_list * rl = NULL; rl = BUS_GET_RESOURCE_LIST(dev, child); if (!rl) return (EINVAL); resource_list_add(rl, type, rid, start, (start + count - 1), count); return (0); } /** * @brief Helper function for implementing BUS_DELETE_RESOURCE(). * * This implementation of BUS_DELETE_RESOURCE() uses the * resource_list_delete() function to do most of the work. It calls * BUS_GET_RESOURCE_LIST() to find a suitable resource list to * edit. */ void bus_generic_rl_delete_resource(device_t dev, device_t child, int type, int rid) { struct resource_list * rl = NULL; rl = BUS_GET_RESOURCE_LIST(dev, child); if (!rl) return; resource_list_delete(rl, type, rid); return; } /** * @brief Helper function for implementing BUS_RELEASE_RESOURCE(). * * This implementation of BUS_RELEASE_RESOURCE() uses the * resource_list_release() function to do most of the work. It calls * BUS_GET_RESOURCE_LIST() to find a suitable resource list. */ int bus_generic_rl_release_resource(device_t dev, device_t child, struct resource *r) { struct resource_list * rl = NULL; if (device_get_parent(child) != dev) return (BUS_RELEASE_RESOURCE(device_get_parent(dev), child, r)); rl = BUS_GET_RESOURCE_LIST(dev, child); if (!rl) return (EINVAL); return (resource_list_release(rl, dev, child, r)); } /** * @brief Helper function for implementing BUS_ALLOC_RESOURCE(). * * This implementation of BUS_ALLOC_RESOURCE() uses the * resource_list_alloc() function to do most of the work. It calls * BUS_GET_RESOURCE_LIST() to find a suitable resource list. */ struct resource * bus_generic_rl_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct resource_list * rl = NULL; if (device_get_parent(child) != dev) return (BUS_ALLOC_RESOURCE(device_get_parent(dev), child, type, rid, start, end, count, flags)); rl = BUS_GET_RESOURCE_LIST(dev, child); if (!rl) return (NULL); return (resource_list_alloc(rl, dev, child, type, rid, start, end, count, flags)); } /** * @brief Helper function for implementing BUS_ALLOC_RESOURCE(). * * This implementation of BUS_ALLOC_RESOURCE() allocates a * resource from a resource manager. It uses BUS_GET_RMAN() * to obtain the resource manager. */ struct resource * bus_generic_rman_alloc_resource(device_t dev, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct resource *r; struct rman *rm; rm = BUS_GET_RMAN(dev, type, flags); if (rm == NULL) return (NULL); r = rman_reserve_resource(rm, start, end, count, flags & ~RF_ACTIVE, child); if (r == NULL) return (NULL); rman_set_rid(r, *rid); rman_set_type(r, type); if (flags & RF_ACTIVE) { if (bus_activate_resource(child, type, *rid, r) != 0) { rman_release_resource(r); return (NULL); } } return (r); } /** * @brief Helper function for implementing BUS_ADJUST_RESOURCE(). * * This implementation of BUS_ADJUST_RESOURCE() adjusts resources only * if they were allocated from the resource manager returned by * BUS_GET_RMAN(). */ int bus_generic_rman_adjust_resource(device_t dev, device_t child, struct resource *r, rman_res_t start, rman_res_t end) { struct rman *rm; rm = BUS_GET_RMAN(dev, rman_get_type(r), rman_get_flags(r)); if (rm == NULL) return (ENXIO); if (!rman_is_region_manager(r, rm)) return (EINVAL); return (rman_adjust_resource(r, start, end)); } /** * @brief Helper function for implementing BUS_RELEASE_RESOURCE(). * * This implementation of BUS_RELEASE_RESOURCE() releases resources * allocated by bus_generic_rman_alloc_resource. */ int bus_generic_rman_release_resource(device_t dev, device_t child, struct resource *r) { #ifdef INVARIANTS struct rman *rm; #endif int error; #ifdef INVARIANTS rm = BUS_GET_RMAN(dev, rman_get_type(r), rman_get_flags(r)); KASSERT(rman_is_region_manager(r, rm), ("%s: rman %p doesn't match for resource %p", __func__, rm, r)); #endif if (rman_get_flags(r) & RF_ACTIVE) { error = bus_deactivate_resource(child, r); if (error != 0) return (error); } return (rman_release_resource(r)); } /** * @brief Helper function for implementing BUS_ACTIVATE_RESOURCE(). * * This implementation of BUS_ACTIVATE_RESOURCE() activates resources * allocated by bus_generic_rman_alloc_resource. */ int bus_generic_rman_activate_resource(device_t dev, device_t child, struct resource *r) { struct resource_map map; #ifdef INVARIANTS struct rman *rm; #endif int error, type; type = rman_get_type(r); #ifdef INVARIANTS rm = BUS_GET_RMAN(dev, type, rman_get_flags(r)); KASSERT(rman_is_region_manager(r, rm), ("%s: rman %p doesn't match for resource %p", __func__, rm, r)); #endif error = rman_activate_resource(r); if (error != 0) return (error); switch (type) { case SYS_RES_IOPORT: case SYS_RES_MEMORY: if ((rman_get_flags(r) & RF_UNMAPPED) == 0) { error = BUS_MAP_RESOURCE(dev, child, r, NULL, &map); if (error != 0) break; rman_set_mapping(r, &map); } break; #ifdef INTRNG case SYS_RES_IRQ: error = intr_activate_irq(child, r); break; #endif } if (error != 0) rman_deactivate_resource(r); return (error); } /** * @brief Helper function for implementing BUS_DEACTIVATE_RESOURCE(). * * This implementation of BUS_DEACTIVATE_RESOURCE() deactivates * resources allocated by bus_generic_rman_alloc_resource. */ int bus_generic_rman_deactivate_resource(device_t dev, device_t child, struct resource *r) { struct resource_map map; #ifdef INVARIANTS struct rman *rm; #endif int error, type; type = rman_get_type(r); #ifdef INVARIANTS rm = BUS_GET_RMAN(dev, type, rman_get_flags(r)); KASSERT(rman_is_region_manager(r, rm), ("%s: rman %p doesn't match for resource %p", __func__, rm, r)); #endif error = rman_deactivate_resource(r); if (error != 0) return (error); switch (type) { case SYS_RES_IOPORT: case SYS_RES_MEMORY: if ((rman_get_flags(r) & RF_UNMAPPED) == 0) { rman_get_mapping(r, &map); BUS_UNMAP_RESOURCE(dev, child, r, &map); } break; #ifdef INTRNG case SYS_RES_IRQ: intr_deactivate_irq(child, r); break; #endif } return (0); } /** * @brief Helper function for implementing BUS_CHILD_PRESENT(). * * This simple implementation of BUS_CHILD_PRESENT() simply calls the * BUS_CHILD_PRESENT() method of the parent of @p dev. */ int bus_generic_child_present(device_t dev, device_t child) { return (BUS_CHILD_PRESENT(device_get_parent(dev), dev)); } /** * @brief Helper function for implementing BUS_GET_DOMAIN(). * * This simple implementation of BUS_GET_DOMAIN() calls the * BUS_GET_DOMAIN() method of the parent of @p dev. If @p dev * does not have a parent, the function fails with ENOENT. */ int bus_generic_get_domain(device_t dev, device_t child, int *domain) { if (dev->parent) return (BUS_GET_DOMAIN(dev->parent, dev, domain)); return (ENOENT); } /** * @brief Helper function to implement normal BUS_GET_DEVICE_PATH() * * This function knows how to (a) pass the request up the tree if there's * a parent and (b) Knows how to supply a FreeBSD locator. * * @param bus bus in the walk up the tree * @param child leaf node to print information about * @param locator BUS_LOCATOR_xxx string for locator * @param sb Buffer to print information into */ int bus_generic_get_device_path(device_t bus, device_t child, const char *locator, struct sbuf *sb) { int rv = 0; device_t parent; /* * We don't recurse on ACPI since either we know the handle for the * device or we don't. And if we're in the generic routine, we don't * have a ACPI override. All other locators build up a path by having * their parents create a path and then adding the path element for this * node. That's why we recurse with parent, bus rather than the typical * parent, child: each spot in the tree is independent of what our child * will do with this path. */ parent = device_get_parent(bus); if (parent != NULL && strcmp(locator, BUS_LOCATOR_ACPI) != 0) { rv = BUS_GET_DEVICE_PATH(parent, bus, locator, sb); } if (strcmp(locator, BUS_LOCATOR_FREEBSD) == 0) { if (rv == 0) { sbuf_printf(sb, "/%s", device_get_nameunit(child)); } return (rv); } /* * Don't know what to do. So assume we do nothing. Not sure that's * the right thing, but keeps us from having a big list here. */ return (0); } /** * @brief Helper function for implementing BUS_RESCAN(). * * This null implementation of BUS_RESCAN() always fails to indicate * the bus does not support rescanning. */ int bus_null_rescan(device_t dev) { return (ENODEV); } /* * Some convenience functions to make it easier for drivers to use the * resource-management functions. All these really do is hide the * indirection through the parent's method table, making for slightly * less-wordy code. In the future, it might make sense for this code * to maintain some sort of a list of resources allocated by each device. */ int bus_alloc_resources(device_t dev, struct resource_spec *rs, struct resource **res) { int i; for (i = 0; rs[i].type != -1; i++) res[i] = NULL; for (i = 0; rs[i].type != -1; i++) { res[i] = bus_alloc_resource_any(dev, rs[i].type, &rs[i].rid, rs[i].flags); if (res[i] == NULL && !(rs[i].flags & RF_OPTIONAL)) { bus_release_resources(dev, rs, res); return (ENXIO); } } return (0); } void bus_release_resources(device_t dev, const struct resource_spec *rs, struct resource **res) { int i; for (i = 0; rs[i].type != -1; i++) if (res[i] != NULL) { bus_release_resource( dev, rs[i].type, rs[i].rid, res[i]); res[i] = NULL; } } /** * @brief Wrapper function for BUS_ALLOC_RESOURCE(). * * This function simply calls the BUS_ALLOC_RESOURCE() method of the * parent of @p dev. */ struct resource * bus_alloc_resource(device_t dev, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct resource *res; if (dev->parent == NULL) return (NULL); res = BUS_ALLOC_RESOURCE(dev->parent, dev, type, rid, start, end, count, flags); return (res); } /** * @brief Wrapper function for BUS_ADJUST_RESOURCE(). * * This function simply calls the BUS_ADJUST_RESOURCE() method of the * parent of @p dev. */ int bus_adjust_resource(device_t dev, struct resource *r, rman_res_t start, rman_res_t end) { if (dev->parent == NULL) return (EINVAL); return (BUS_ADJUST_RESOURCE(dev->parent, dev, r, start, end)); } int bus_adjust_resource_old(device_t dev, int type __unused, struct resource *r, rman_res_t start, rman_res_t end) { return (bus_adjust_resource(dev, r, start, end)); } /** * @brief Wrapper function for BUS_TRANSLATE_RESOURCE(). * * This function simply calls the BUS_TRANSLATE_RESOURCE() method of the * parent of @p dev. */ int bus_translate_resource(device_t dev, int type, rman_res_t start, rman_res_t *newstart) { if (dev->parent == NULL) return (EINVAL); return (BUS_TRANSLATE_RESOURCE(dev->parent, type, start, newstart)); } /** * @brief Wrapper function for BUS_ACTIVATE_RESOURCE(). * * This function simply calls the BUS_ACTIVATE_RESOURCE() method of the * parent of @p dev. */ int bus_activate_resource(device_t dev, struct resource *r) { if (dev->parent == NULL) return (EINVAL); return (BUS_ACTIVATE_RESOURCE(dev->parent, dev, r)); } int bus_activate_resource_old(device_t dev, int type, int rid, struct resource *r) { return (bus_activate_resource(dev, r)); } /** * @brief Wrapper function for BUS_DEACTIVATE_RESOURCE(). * * This function simply calls the BUS_DEACTIVATE_RESOURCE() method of the * parent of @p dev. */ int bus_deactivate_resource(device_t dev, struct resource *r) { if (dev->parent == NULL) return (EINVAL); return (BUS_DEACTIVATE_RESOURCE(dev->parent, dev, r)); } int bus_deactivate_resource_old(device_t dev, int type, int rid, struct resource *r) { return (bus_deactivate_resource(dev, r)); } /** * @brief Wrapper function for BUS_MAP_RESOURCE(). * * This function simply calls the BUS_MAP_RESOURCE() method of the * parent of @p dev. */ int bus_map_resource(device_t dev, struct resource *r, struct resource_map_request *args, struct resource_map *map) { if (dev->parent == NULL) return (EINVAL); return (BUS_MAP_RESOURCE(dev->parent, dev, r, args, map)); } int bus_map_resource_old(device_t dev, int type, struct resource *r, struct resource_map_request *args, struct resource_map *map) { return (bus_map_resource(dev, r, args, map)); } /** * @brief Wrapper function for BUS_UNMAP_RESOURCE(). * * This function simply calls the BUS_UNMAP_RESOURCE() method of the * parent of @p dev. */ int bus_unmap_resource(device_t dev, struct resource *r, struct resource_map *map) { if (dev->parent == NULL) return (EINVAL); return (BUS_UNMAP_RESOURCE(dev->parent, dev, r, map)); } int bus_unmap_resource_old(device_t dev, int type, struct resource *r, struct resource_map *map) { return (bus_unmap_resource(dev, r, map)); } /** * @brief Wrapper function for BUS_RELEASE_RESOURCE(). * * This function simply calls the BUS_RELEASE_RESOURCE() method of the * parent of @p dev. */ int bus_release_resource(device_t dev, struct resource *r) { int rv; if (dev->parent == NULL) return (EINVAL); rv = BUS_RELEASE_RESOURCE(dev->parent, dev, r); return (rv); } int bus_release_resource_old(device_t dev, int type, int rid, struct resource *r) { return (bus_release_resource(dev, r)); } /** * @brief Wrapper function for BUS_SETUP_INTR(). * * This function simply calls the BUS_SETUP_INTR() method of the * parent of @p dev. */ int bus_setup_intr(device_t dev, struct resource *r, int flags, driver_filter_t filter, driver_intr_t handler, void *arg, void **cookiep) { int error; if (dev->parent == NULL) return (EINVAL); error = BUS_SETUP_INTR(dev->parent, dev, r, flags, filter, handler, arg, cookiep); if (error != 0) return (error); if (handler != NULL && !(flags & INTR_MPSAFE)) device_printf(dev, "[GIANT-LOCKED]\n"); return (0); } /** * @brief Wrapper function for BUS_TEARDOWN_INTR(). * * This function simply calls the BUS_TEARDOWN_INTR() method of the * parent of @p dev. */ int bus_teardown_intr(device_t dev, struct resource *r, void *cookie) { if (dev->parent == NULL) return (EINVAL); return (BUS_TEARDOWN_INTR(dev->parent, dev, r, cookie)); } /** * @brief Wrapper function for BUS_SUSPEND_INTR(). * * This function simply calls the BUS_SUSPEND_INTR() method of the * parent of @p dev. */ int bus_suspend_intr(device_t dev, struct resource *r) { if (dev->parent == NULL) return (EINVAL); return (BUS_SUSPEND_INTR(dev->parent, dev, r)); } /** * @brief Wrapper function for BUS_RESUME_INTR(). * * This function simply calls the BUS_RESUME_INTR() method of the * parent of @p dev. */ int bus_resume_intr(device_t dev, struct resource *r) { if (dev->parent == NULL) return (EINVAL); return (BUS_RESUME_INTR(dev->parent, dev, r)); } /** * @brief Wrapper function for BUS_BIND_INTR(). * * This function simply calls the BUS_BIND_INTR() method of the * parent of @p dev. */ int bus_bind_intr(device_t dev, struct resource *r, int cpu) { if (dev->parent == NULL) return (EINVAL); return (BUS_BIND_INTR(dev->parent, dev, r, cpu)); } /** * @brief Wrapper function for BUS_DESCRIBE_INTR(). * * This function first formats the requested description into a * temporary buffer and then calls the BUS_DESCRIBE_INTR() method of * the parent of @p dev. */ int bus_describe_intr(device_t dev, struct resource *irq, void *cookie, const char *fmt, ...) { va_list ap; char descr[MAXCOMLEN + 1]; if (dev->parent == NULL) return (EINVAL); va_start(ap, fmt); vsnprintf(descr, sizeof(descr), fmt, ap); va_end(ap); return (BUS_DESCRIBE_INTR(dev->parent, dev, irq, cookie, descr)); } /** * @brief Wrapper function for BUS_SET_RESOURCE(). * * This function simply calls the BUS_SET_RESOURCE() method of the * parent of @p dev. */ int bus_set_resource(device_t dev, int type, int rid, rman_res_t start, rman_res_t count) { return (BUS_SET_RESOURCE(device_get_parent(dev), dev, type, rid, start, count)); } /** * @brief Wrapper function for BUS_GET_RESOURCE(). * * This function simply calls the BUS_GET_RESOURCE() method of the * parent of @p dev. */ int bus_get_resource(device_t dev, int type, int rid, rman_res_t *startp, rman_res_t *countp) { return (BUS_GET_RESOURCE(device_get_parent(dev), dev, type, rid, startp, countp)); } /** * @brief Wrapper function for BUS_GET_RESOURCE(). * * This function simply calls the BUS_GET_RESOURCE() method of the * parent of @p dev and returns the start value. */ rman_res_t bus_get_resource_start(device_t dev, int type, int rid) { rman_res_t start; rman_res_t count; int error; error = BUS_GET_RESOURCE(device_get_parent(dev), dev, type, rid, &start, &count); if (error) return (0); return (start); } /** * @brief Wrapper function for BUS_GET_RESOURCE(). * * This function simply calls the BUS_GET_RESOURCE() method of the * parent of @p dev and returns the count value. */ rman_res_t bus_get_resource_count(device_t dev, int type, int rid) { rman_res_t start; rman_res_t count; int error; error = BUS_GET_RESOURCE(device_get_parent(dev), dev, type, rid, &start, &count); if (error) return (0); return (count); } /** * @brief Wrapper function for BUS_DELETE_RESOURCE(). * * This function simply calls the BUS_DELETE_RESOURCE() method of the * parent of @p dev. */ void bus_delete_resource(device_t dev, int type, int rid) { BUS_DELETE_RESOURCE(device_get_parent(dev), dev, type, rid); } /** * @brief Wrapper function for BUS_CHILD_PRESENT(). * * This function simply calls the BUS_CHILD_PRESENT() method of the * parent of @p dev. */ int bus_child_present(device_t child) { return (BUS_CHILD_PRESENT(device_get_parent(child), child)); } /** * @brief Wrapper function for BUS_CHILD_PNPINFO(). * * This function simply calls the BUS_CHILD_PNPINFO() method of the parent of @p * dev. */ int bus_child_pnpinfo(device_t child, struct sbuf *sb) { device_t parent; parent = device_get_parent(child); if (parent == NULL) return (0); return (BUS_CHILD_PNPINFO(parent, child, sb)); } /** * @brief Generic implementation that does nothing for bus_child_pnpinfo * * This function has the right signature and returns 0 since the sbuf is passed * to us to append to. */ int bus_generic_child_pnpinfo(device_t dev, device_t child, struct sbuf *sb) { return (0); } /** * @brief Wrapper function for BUS_CHILD_LOCATION(). * * This function simply calls the BUS_CHILD_LOCATION() method of the parent of * @p dev. */ int bus_child_location(device_t child, struct sbuf *sb) { device_t parent; parent = device_get_parent(child); if (parent == NULL) return (0); return (BUS_CHILD_LOCATION(parent, child, sb)); } /** * @brief Generic implementation that does nothing for bus_child_location * * This function has the right signature and returns 0 since the sbuf is passed * to us to append to. */ int bus_generic_child_location(device_t dev, device_t child, struct sbuf *sb) { return (0); } /** * @brief Wrapper function for BUS_GET_CPUS(). * * This function simply calls the BUS_GET_CPUS() method of the * parent of @p dev. */ int bus_get_cpus(device_t dev, enum cpu_sets op, size_t setsize, cpuset_t *cpuset) { device_t parent; parent = device_get_parent(dev); if (parent == NULL) return (EINVAL); return (BUS_GET_CPUS(parent, dev, op, setsize, cpuset)); } /** * @brief Wrapper function for BUS_GET_DMA_TAG(). * * This function simply calls the BUS_GET_DMA_TAG() method of the * parent of @p dev. */ bus_dma_tag_t bus_get_dma_tag(device_t dev) { device_t parent; parent = device_get_parent(dev); if (parent == NULL) return (NULL); return (BUS_GET_DMA_TAG(parent, dev)); } /** * @brief Wrapper function for BUS_GET_BUS_TAG(). * * This function simply calls the BUS_GET_BUS_TAG() method of the * parent of @p dev. */ bus_space_tag_t bus_get_bus_tag(device_t dev) { device_t parent; parent = device_get_parent(dev); if (parent == NULL) return ((bus_space_tag_t)0); return (BUS_GET_BUS_TAG(parent, dev)); } /** * @brief Wrapper function for BUS_GET_DOMAIN(). * * This function simply calls the BUS_GET_DOMAIN() method of the * parent of @p dev. */ int bus_get_domain(device_t dev, int *domain) { return (BUS_GET_DOMAIN(device_get_parent(dev), dev, domain)); } /* Resume all devices and then notify userland that we're up again. */ static int root_resume(device_t dev) { int error; error = bus_generic_resume(dev); if (error == 0) { devctl_notify("kernel", "power", "resume", NULL); } return (error); } static int root_print_child(device_t dev, device_t child) { int retval = 0; retval += bus_print_child_header(dev, child); retval += printf("\n"); return (retval); } static int root_setup_intr(device_t dev, device_t child, struct resource *irq, int flags, driver_filter_t *filter, driver_intr_t *intr, void *arg, void **cookiep) { /* * If an interrupt mapping gets to here something bad has happened. */ panic("root_setup_intr"); } /* * If we get here, assume that the device is permanent and really is * present in the system. Removable bus drivers are expected to intercept * this call long before it gets here. We return -1 so that drivers that * really care can check vs -1 or some ERRNO returned higher in the food * chain. */ static int root_child_present(device_t dev, device_t child) { return (-1); } static int root_get_cpus(device_t dev, device_t child, enum cpu_sets op, size_t setsize, cpuset_t *cpuset) { switch (op) { case INTR_CPUS: /* Default to returning the set of all CPUs. */ if (setsize != sizeof(cpuset_t)) return (EINVAL); *cpuset = all_cpus; return (0); default: return (EINVAL); } } static kobj_method_t root_methods[] = { /* Device interface */ KOBJMETHOD(device_shutdown, bus_generic_shutdown), KOBJMETHOD(device_suspend, bus_generic_suspend), KOBJMETHOD(device_resume, root_resume), /* Bus interface */ KOBJMETHOD(bus_print_child, root_print_child), KOBJMETHOD(bus_read_ivar, bus_generic_read_ivar), KOBJMETHOD(bus_write_ivar, bus_generic_write_ivar), KOBJMETHOD(bus_setup_intr, root_setup_intr), KOBJMETHOD(bus_child_present, root_child_present), KOBJMETHOD(bus_get_cpus, root_get_cpus), KOBJMETHOD_END }; static driver_t root_driver = { "root", root_methods, 1, /* no softc */ }; device_t root_bus; devclass_t root_devclass; static int root_bus_module_handler(module_t mod, int what, void* arg) { switch (what) { case MOD_LOAD: TAILQ_INIT(&bus_data_devices); kobj_class_compile((kobj_class_t) &root_driver); root_bus = make_device(NULL, "root", 0); root_bus->desc = "System root bus"; kobj_init((kobj_t) root_bus, (kobj_class_t) &root_driver); root_bus->driver = &root_driver; root_bus->state = DS_ATTACHED; root_devclass = devclass_find_internal("root", NULL, FALSE); devctl2_init(); return (0); case MOD_SHUTDOWN: device_shutdown(root_bus); return (0); default: return (EOPNOTSUPP); } return (0); } static moduledata_t root_bus_mod = { "rootbus", root_bus_module_handler, NULL }; DECLARE_MODULE(rootbus, root_bus_mod, SI_SUB_DRIVERS, SI_ORDER_FIRST); /** * @brief Automatically configure devices * * This function begins the autoconfiguration process by calling * device_probe_and_attach() for each child of the @c root0 device. */ void root_bus_configure(void) { PDEBUG((".")); /* Eventually this will be split up, but this is sufficient for now. */ bus_set_pass(BUS_PASS_DEFAULT); } /** * @brief Module handler for registering device drivers * * This module handler is used to automatically register device * drivers when modules are loaded. If @p what is MOD_LOAD, it calls * devclass_add_driver() for the driver described by the * driver_module_data structure pointed to by @p arg */ int driver_module_handler(module_t mod, int what, void *arg) { struct driver_module_data *dmd; devclass_t bus_devclass; kobj_class_t driver; int error, pass; dmd = (struct driver_module_data *)arg; bus_devclass = devclass_find_internal(dmd->dmd_busname, NULL, TRUE); error = 0; switch (what) { case MOD_LOAD: if (dmd->dmd_chainevh) error = dmd->dmd_chainevh(mod,what,dmd->dmd_chainarg); pass = dmd->dmd_pass; driver = dmd->dmd_driver; PDEBUG(("Loading module: driver %s on bus %s (pass %d)", DRIVERNAME(driver), dmd->dmd_busname, pass)); error = devclass_add_driver(bus_devclass, driver, pass, dmd->dmd_devclass); break; case MOD_UNLOAD: PDEBUG(("Unloading module: driver %s from bus %s", DRIVERNAME(dmd->dmd_driver), dmd->dmd_busname)); error = devclass_delete_driver(bus_devclass, dmd->dmd_driver); if (!error && dmd->dmd_chainevh) error = dmd->dmd_chainevh(mod,what,dmd->dmd_chainarg); break; case MOD_QUIESCE: PDEBUG(("Quiesce module: driver %s from bus %s", DRIVERNAME(dmd->dmd_driver), dmd->dmd_busname)); error = devclass_quiesce_driver(bus_devclass, dmd->dmd_driver); if (!error && dmd->dmd_chainevh) error = dmd->dmd_chainevh(mod,what,dmd->dmd_chainarg); break; default: error = EOPNOTSUPP; break; } return (error); } /** * @brief Enumerate all hinted devices for this bus. * * Walks through the hints for this bus and calls the bus_hinted_child * routine for each one it fines. It searches first for the specific * bus that's being probed for hinted children (eg isa0), and then for * generic children (eg isa). * * @param dev bus device to enumerate */ void bus_enumerate_hinted_children(device_t bus) { int i; const char *dname, *busname; int dunit; /* * enumerate all devices on the specific bus */ busname = device_get_nameunit(bus); i = 0; while (resource_find_match(&i, &dname, &dunit, "at", busname) == 0) BUS_HINTED_CHILD(bus, dname, dunit); /* * and all the generic ones. */ busname = device_get_name(bus); i = 0; while (resource_find_match(&i, &dname, &dunit, "at", busname) == 0) BUS_HINTED_CHILD(bus, dname, dunit); } #ifdef BUS_DEBUG /* the _short versions avoid iteration by not calling anything that prints * more than oneliners. I love oneliners. */ static void print_device_short(device_t dev, int indent) { if (!dev) return; indentprintf(("device %d: <%s> %sparent,%schildren,%s%s%s%s%s,%sivars,%ssoftc,busy=%d\n", dev->unit, dev->desc, (dev->parent? "":"no "), (TAILQ_EMPTY(&dev->children)? "no ":""), (dev->flags&DF_ENABLED? "enabled,":"disabled,"), (dev->flags&DF_FIXEDCLASS? "fixed,":""), (dev->flags&DF_WILDCARD? "wildcard,":""), (dev->flags&DF_DESCMALLOCED? "descmalloced,":""), (dev->flags&DF_SUSPENDED? "suspended,":""), (dev->ivars? "":"no "), (dev->softc? "":"no "), dev->busy)); } static void print_device(device_t dev, int indent) { if (!dev) return; print_device_short(dev, indent); indentprintf(("Parent:\n")); print_device_short(dev->parent, indent+1); indentprintf(("Driver:\n")); print_driver_short(dev->driver, indent+1); indentprintf(("Devclass:\n")); print_devclass_short(dev->devclass, indent+1); } void print_device_tree_short(device_t dev, int indent) /* print the device and all its children (indented) */ { device_t child; if (!dev) return; print_device_short(dev, indent); TAILQ_FOREACH(child, &dev->children, link) { print_device_tree_short(child, indent+1); } } void print_device_tree(device_t dev, int indent) /* print the device and all its children (indented) */ { device_t child; if (!dev) return; print_device(dev, indent); TAILQ_FOREACH(child, &dev->children, link) { print_device_tree(child, indent+1); } } static void print_driver_short(driver_t *driver, int indent) { if (!driver) return; indentprintf(("driver %s: softc size = %zd\n", driver->name, driver->size)); } static void print_driver(driver_t *driver, int indent) { if (!driver) return; print_driver_short(driver, indent); } static void print_driver_list(driver_list_t drivers, int indent) { driverlink_t driver; TAILQ_FOREACH(driver, &drivers, link) { print_driver(driver->driver, indent); } } static void print_devclass_short(devclass_t dc, int indent) { if ( !dc ) return; indentprintf(("devclass %s: max units = %d\n", dc->name, dc->maxunit)); } static void print_devclass(devclass_t dc, int indent) { int i; if ( !dc ) return; print_devclass_short(dc, indent); indentprintf(("Drivers:\n")); print_driver_list(dc->drivers, indent+1); indentprintf(("Devices:\n")); for (i = 0; i < dc->maxunit; i++) if (dc->devices[i]) print_device(dc->devices[i], indent+1); } void print_devclass_list_short(void) { devclass_t dc; printf("Short listing of devclasses, drivers & devices:\n"); TAILQ_FOREACH(dc, &devclasses, link) { print_devclass_short(dc, 0); } } void print_devclass_list(void) { devclass_t dc; printf("Full listing of devclasses, drivers & devices:\n"); TAILQ_FOREACH(dc, &devclasses, link) { print_devclass(dc, 0); } } #endif /* * User-space access to the device tree. * * We implement a small set of nodes: * * hw.bus Single integer read method to obtain the * current generation count. * hw.bus.devices Reads the entire device tree in flat space. * hw.bus.rman Resource manager interface * * We might like to add the ability to scan devclasses and/or drivers to * determine what else is currently loaded/available. */ static int sysctl_bus_info(SYSCTL_HANDLER_ARGS) { struct u_businfo ubus; ubus.ub_version = BUS_USER_VERSION; ubus.ub_generation = bus_data_generation; return (SYSCTL_OUT(req, &ubus, sizeof(ubus))); } SYSCTL_PROC(_hw_bus, OID_AUTO, info, CTLTYPE_STRUCT | CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, 0, sysctl_bus_info, "S,u_businfo", "bus-related data"); static int sysctl_devices(SYSCTL_HANDLER_ARGS) { struct sbuf sb; int *name = (int *)arg1; u_int namelen = arg2; int index; device_t dev; struct u_device *udev; int error; if (namelen != 2) return (EINVAL); if (bus_data_generation_check(name[0])) return (EINVAL); index = name[1]; /* * Scan the list of devices, looking for the requested index. */ TAILQ_FOREACH(dev, &bus_data_devices, devlink) { if (index-- == 0) break; } if (dev == NULL) return (ENOENT); /* * Populate the return item, careful not to overflow the buffer. */ udev = malloc(sizeof(*udev), M_BUS, M_WAITOK | M_ZERO); udev->dv_handle = (uintptr_t)dev; udev->dv_parent = (uintptr_t)dev->parent; udev->dv_devflags = dev->devflags; udev->dv_flags = dev->flags; udev->dv_state = dev->state; sbuf_new(&sb, udev->dv_fields, sizeof(udev->dv_fields), SBUF_FIXEDLEN); if (dev->nameunit != NULL) sbuf_cat(&sb, dev->nameunit); sbuf_putc(&sb, '\0'); if (dev->desc != NULL) sbuf_cat(&sb, dev->desc); sbuf_putc(&sb, '\0'); if (dev->driver != NULL) sbuf_cat(&sb, dev->driver->name); sbuf_putc(&sb, '\0'); bus_child_pnpinfo(dev, &sb); sbuf_putc(&sb, '\0'); bus_child_location(dev, &sb); sbuf_putc(&sb, '\0'); error = sbuf_finish(&sb); if (error == 0) error = SYSCTL_OUT(req, udev, sizeof(*udev)); sbuf_delete(&sb); free(udev, M_BUS); return (error); } SYSCTL_NODE(_hw_bus, OID_AUTO, devices, CTLFLAG_RD | CTLFLAG_NEEDGIANT, sysctl_devices, "system device tree"); int bus_data_generation_check(int generation) { if (generation != bus_data_generation) return (1); /* XXX generate optimised lists here? */ return (0); } void bus_data_generation_update(void) { atomic_add_int(&bus_data_generation, 1); } int bus_free_resource(device_t dev, int type, struct resource *r) { if (r == NULL) return (0); return (bus_release_resource(dev, type, rman_get_rid(r), r)); } device_t device_lookup_by_name(const char *name) { device_t dev; TAILQ_FOREACH(dev, &bus_data_devices, devlink) { if (dev->nameunit != NULL && strcmp(dev->nameunit, name) == 0) return (dev); } return (NULL); } /* * /dev/devctl2 implementation. The existing /dev/devctl device has * implicit semantics on open, so it could not be reused for this. * Another option would be to call this /dev/bus? */ static int find_device(struct devreq *req, device_t *devp) { device_t dev; /* * First, ensure that the name is nul terminated. */ if (memchr(req->dr_name, '\0', sizeof(req->dr_name)) == NULL) return (EINVAL); /* * Second, try to find an attached device whose name matches * 'name'. */ dev = device_lookup_by_name(req->dr_name); if (dev != NULL) { *devp = dev; return (0); } /* Finally, give device enumerators a chance. */ dev = NULL; EVENTHANDLER_DIRECT_INVOKE(dev_lookup, req->dr_name, &dev); if (dev == NULL) return (ENOENT); *devp = dev; return (0); } static bool driver_exists(device_t bus, const char *driver) { devclass_t dc; for (dc = bus->devclass; dc != NULL; dc = dc->parent) { if (devclass_find_driver_internal(dc, driver) != NULL) return (true); } return (false); } static void device_gen_nomatch(device_t dev) { device_t child; if (dev->flags & DF_NEEDNOMATCH && dev->state == DS_NOTPRESENT) { device_handle_nomatch(dev); } dev->flags &= ~DF_NEEDNOMATCH; TAILQ_FOREACH(child, &dev->children, link) { device_gen_nomatch(child); } } static void device_do_deferred_actions(void) { devclass_t dc; driverlink_t dl; /* * Walk through the devclasses to find all the drivers we've tagged as * deferred during the freeze and call the driver added routines. They * have already been added to the lists in the background, so the driver * added routines that trigger a probe will have all the right bidders * for the probe auction. */ TAILQ_FOREACH(dc, &devclasses, link) { TAILQ_FOREACH(dl, &dc->drivers, link) { if (dl->flags & DL_DEFERRED_PROBE) { devclass_driver_added(dc, dl->driver); dl->flags &= ~DL_DEFERRED_PROBE; } } } /* * We also defer no-match events during a freeze. Walk the tree and * generate all the pent-up events that are still relevant. */ device_gen_nomatch(root_bus); bus_data_generation_update(); } static int device_get_path(device_t dev, const char *locator, struct sbuf *sb) { device_t parent; int error; KASSERT(sb != NULL, ("sb is NULL")); parent = device_get_parent(dev); if (parent == NULL) { error = sbuf_putc(sb, '/'); } else { error = BUS_GET_DEVICE_PATH(parent, dev, locator, sb); if (error == 0) { error = sbuf_error(sb); if (error == 0 && sbuf_len(sb) <= 1) error = EIO; } } sbuf_finish(sb); return (error); } static int devctl2_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag, struct thread *td) { struct devreq *req; device_t dev; int error, old; /* Locate the device to control. */ bus_topo_lock(); req = (struct devreq *)data; switch (cmd) { case DEV_ATTACH: case DEV_DETACH: case DEV_ENABLE: case DEV_DISABLE: case DEV_SUSPEND: case DEV_RESUME: case DEV_SET_DRIVER: case DEV_CLEAR_DRIVER: case DEV_RESCAN: case DEV_DELETE: case DEV_RESET: error = priv_check(td, PRIV_DRIVER); if (error == 0) error = find_device(req, &dev); break; case DEV_FREEZE: case DEV_THAW: error = priv_check(td, PRIV_DRIVER); break; case DEV_GET_PATH: error = find_device(req, &dev); break; default: error = ENOTTY; break; } if (error) { bus_topo_unlock(); return (error); } /* Perform the requested operation. */ switch (cmd) { case DEV_ATTACH: if (device_is_attached(dev)) error = EBUSY; else if (!device_is_enabled(dev)) error = ENXIO; else error = device_probe_and_attach(dev); break; case DEV_DETACH: if (!device_is_attached(dev)) { error = ENXIO; break; } if (!(req->dr_flags & DEVF_FORCE_DETACH)) { error = device_quiesce(dev); if (error) break; } error = device_detach(dev); break; case DEV_ENABLE: if (device_is_enabled(dev)) { error = EBUSY; break; } /* * If the device has been probed but not attached (e.g. * when it has been disabled by a loader hint), just * attach the device rather than doing a full probe. */ device_enable(dev); if (dev->devclass != NULL) { /* * If the device was disabled via a hint, clear * the hint. */ if (resource_disabled(dev->devclass->name, dev->unit)) resource_unset_value(dev->devclass->name, dev->unit, "disabled"); /* Allow any drivers to rebid. */ if (!(dev->flags & DF_FIXEDCLASS)) devclass_delete_device(dev->devclass, dev); } error = device_probe_and_attach(dev); break; case DEV_DISABLE: if (!device_is_enabled(dev)) { error = ENXIO; break; } if (!(req->dr_flags & DEVF_FORCE_DETACH)) { error = device_quiesce(dev); if (error) break; } /* * Force DF_FIXEDCLASS on around detach to preserve * the existing name. */ old = dev->flags; dev->flags |= DF_FIXEDCLASS; error = device_detach(dev); if (!(old & DF_FIXEDCLASS)) dev->flags &= ~DF_FIXEDCLASS; if (error == 0) device_disable(dev); break; case DEV_SUSPEND: if (device_is_suspended(dev)) { error = EBUSY; break; } if (device_get_parent(dev) == NULL) { error = EINVAL; break; } error = BUS_SUSPEND_CHILD(device_get_parent(dev), dev); break; case DEV_RESUME: if (!device_is_suspended(dev)) { error = EINVAL; break; } if (device_get_parent(dev) == NULL) { error = EINVAL; break; } error = BUS_RESUME_CHILD(device_get_parent(dev), dev); break; case DEV_SET_DRIVER: { devclass_t dc; char driver[128]; error = copyinstr(req->dr_data, driver, sizeof(driver), NULL); if (error) break; if (driver[0] == '\0') { error = EINVAL; break; } if (dev->devclass != NULL && strcmp(driver, dev->devclass->name) == 0) /* XXX: Could possibly force DF_FIXEDCLASS on? */ break; /* * Scan drivers for this device's bus looking for at * least one matching driver. */ if (dev->parent == NULL) { error = EINVAL; break; } if (!driver_exists(dev->parent, driver)) { error = ENOENT; break; } dc = devclass_create(driver); if (dc == NULL) { error = ENOMEM; break; } /* Detach device if necessary. */ if (device_is_attached(dev)) { if (req->dr_flags & DEVF_SET_DRIVER_DETACH) error = device_detach(dev); else error = EBUSY; if (error) break; } /* Clear any previously-fixed device class and unit. */ if (dev->flags & DF_FIXEDCLASS) devclass_delete_device(dev->devclass, dev); dev->flags |= DF_WILDCARD; dev->unit = DEVICE_UNIT_ANY; /* Force the new device class. */ error = devclass_add_device(dc, dev); if (error) break; dev->flags |= DF_FIXEDCLASS; error = device_probe_and_attach(dev); break; } case DEV_CLEAR_DRIVER: if (!(dev->flags & DF_FIXEDCLASS)) { error = 0; break; } if (device_is_attached(dev)) { if (req->dr_flags & DEVF_CLEAR_DRIVER_DETACH) error = device_detach(dev); else error = EBUSY; if (error) break; } dev->flags &= ~DF_FIXEDCLASS; dev->flags |= DF_WILDCARD; devclass_delete_device(dev->devclass, dev); error = device_probe_and_attach(dev); break; case DEV_RESCAN: if (!device_is_attached(dev)) { error = ENXIO; break; } error = BUS_RESCAN(dev); break; case DEV_DELETE: { device_t parent; parent = device_get_parent(dev); if (parent == NULL) { error = EINVAL; break; } if (!(req->dr_flags & DEVF_FORCE_DELETE)) { if (bus_child_present(dev) != 0) { error = EBUSY; break; } } error = device_delete_child(parent, dev); break; } case DEV_FREEZE: if (device_frozen) error = EBUSY; else device_frozen = true; break; case DEV_THAW: if (!device_frozen) error = EBUSY; else { device_do_deferred_actions(); device_frozen = false; } break; case DEV_RESET: if ((req->dr_flags & ~(DEVF_RESET_DETACH)) != 0) { error = EINVAL; break; } if (device_get_parent(dev) == NULL) { error = EINVAL; break; } error = BUS_RESET_CHILD(device_get_parent(dev), dev, req->dr_flags); break; case DEV_GET_PATH: { struct sbuf *sb; char locator[64]; ssize_t len; error = copyinstr(req->dr_buffer.buffer, locator, sizeof(locator), NULL); if (error != 0) break; sb = sbuf_new(NULL, NULL, 0, SBUF_AUTOEXTEND | SBUF_INCLUDENUL /* | SBUF_WAITOK */); error = device_get_path(dev, locator, sb); if (error == 0) { len = sbuf_len(sb); if (req->dr_buffer.length < len) { error = ENAMETOOLONG; } else { error = copyout(sbuf_data(sb), req->dr_buffer.buffer, len); } req->dr_buffer.length = len; } sbuf_delete(sb); break; } } bus_topo_unlock(); return (error); } static struct cdevsw devctl2_cdevsw = { .d_version = D_VERSION, .d_ioctl = devctl2_ioctl, .d_name = "devctl2", }; static void devctl2_init(void) { make_dev_credf(MAKEDEV_ETERNAL, &devctl2_cdevsw, 0, NULL, UID_ROOT, GID_WHEEL, 0644, "devctl2"); } /* * For maintaining device 'at' location info to avoid recomputing it */ struct device_location_node { const char *dln_locator; const char *dln_path; TAILQ_ENTRY(device_location_node) dln_link; }; typedef TAILQ_HEAD(device_location_list, device_location_node) device_location_list_t; struct device_location_cache { device_location_list_t dlc_list; }; /* * Location cache for wired devices. */ device_location_cache_t * dev_wired_cache_init(void) { device_location_cache_t *dcp; dcp = malloc(sizeof(*dcp), M_BUS, M_WAITOK | M_ZERO); TAILQ_INIT(&dcp->dlc_list); return (dcp); } void dev_wired_cache_fini(device_location_cache_t *dcp) { struct device_location_node *dln, *tdln; TAILQ_FOREACH_SAFE(dln, &dcp->dlc_list, dln_link, tdln) { free(dln, M_BUS); } free(dcp, M_BUS); } static struct device_location_node * dev_wired_cache_lookup(device_location_cache_t *dcp, const char *locator) { struct device_location_node *dln; TAILQ_FOREACH(dln, &dcp->dlc_list, dln_link) { if (strcmp(locator, dln->dln_locator) == 0) return (dln); } return (NULL); } static struct device_location_node * dev_wired_cache_add(device_location_cache_t *dcp, const char *locator, const char *path) { struct device_location_node *dln; size_t loclen, pathlen; loclen = strlen(locator) + 1; pathlen = strlen(path) + 1; dln = malloc(sizeof(*dln) + loclen + pathlen, M_BUS, M_WAITOK | M_ZERO); dln->dln_locator = (char *)(dln + 1); memcpy(__DECONST(char *, dln->dln_locator), locator, loclen); dln->dln_path = dln->dln_locator + loclen; memcpy(__DECONST(char *, dln->dln_path), path, pathlen); TAILQ_INSERT_HEAD(&dcp->dlc_list, dln, dln_link); return (dln); } bool dev_wired_cache_match(device_location_cache_t *dcp, device_t dev, const char *at) { struct sbuf *sb; const char *cp; char locator[32]; int error, len; struct device_location_node *res; cp = strchr(at, ':'); if (cp == NULL) return (false); len = cp - at; if (len > sizeof(locator) - 1) /* Skip too long locator */ return (false); memcpy(locator, at, len); locator[len] = '\0'; cp++; error = 0; /* maybe cache this inside device_t and look that up, but not yet */ res = dev_wired_cache_lookup(dcp, locator); if (res == NULL) { sb = sbuf_new(NULL, NULL, 0, SBUF_AUTOEXTEND | SBUF_INCLUDENUL | SBUF_NOWAIT); if (sb != NULL) { error = device_get_path(dev, locator, sb); if (error == 0) { res = dev_wired_cache_add(dcp, locator, sbuf_data(sb)); } sbuf_delete(sb); } } if (error != 0 || res == NULL || res->dln_path == NULL) return (false); return (strcmp(res->dln_path, cp) == 0); } static struct device_prop_elm * device_prop_find(device_t dev, const char *name) { struct device_prop_elm *e; bus_topo_assert(); LIST_FOREACH(e, &dev->props, link) { if (strcmp(name, e->name) == 0) return (e); } return (NULL); } int device_set_prop(device_t dev, const char *name, void *val, device_prop_dtr_t dtr, void *dtr_ctx) { struct device_prop_elm *e, *e1; bus_topo_assert(); e = device_prop_find(dev, name); if (e != NULL) goto found; e1 = malloc(sizeof(*e), M_BUS, M_WAITOK); e = device_prop_find(dev, name); if (e != NULL) { free(e1, M_BUS); goto found; } e1->name = name; e1->val = val; e1->dtr = dtr; e1->dtr_ctx = dtr_ctx; LIST_INSERT_HEAD(&dev->props, e1, link); return (0); found: LIST_REMOVE(e, link); if (e->dtr != NULL) e->dtr(dev, name, e->val, e->dtr_ctx); e->val = val; e->dtr = dtr; e->dtr_ctx = dtr_ctx; LIST_INSERT_HEAD(&dev->props, e, link); return (EEXIST); } int device_get_prop(device_t dev, const char *name, void **valp) { struct device_prop_elm *e; bus_topo_assert(); e = device_prop_find(dev, name); if (e == NULL) return (ENOENT); *valp = e->val; return (0); } int device_clear_prop(device_t dev, const char *name) { struct device_prop_elm *e; bus_topo_assert(); e = device_prop_find(dev, name); if (e == NULL) return (ENOENT); LIST_REMOVE(e, link); if (e->dtr != NULL) e->dtr(dev, e->name, e->val, e->dtr_ctx); free(e, M_BUS); return (0); } static void device_destroy_props(device_t dev) { struct device_prop_elm *e; bus_topo_assert(); while ((e = LIST_FIRST(&dev->props)) != NULL) { LIST_REMOVE_HEAD(&dev->props, link); if (e->dtr != NULL) e->dtr(dev, e->name, e->val, e->dtr_ctx); free(e, M_BUS); } } void device_clear_prop_alldev(const char *name) { device_t dev; TAILQ_FOREACH(dev, &bus_data_devices, devlink) { device_clear_prop(dev, name); } } /* * APIs to manage deprecation and obsolescence. */ static int obsolete_panic = 0; SYSCTL_INT(_debug, OID_AUTO, obsolete_panic, CTLFLAG_RWTUN, &obsolete_panic, 0, "Panic when obsolete features are used (0 = never, 1 = if obsolete, " "2 = if deprecated)"); static void -gone_panic(int major, int running, const char *msg) +gone_panic(int major, int running, const char *msg, ...) { + va_list ap; + switch (obsolete_panic) { case 0: return; case 1: if (running < major) return; /* FALLTHROUGH */ default: - panic("%s", msg); + va_start(ap, msg); + vpanic(msg, ap); } } void -_gone_in(int major, const char *msg) +_gone_in(int major, const char *msg, ...) { - gone_panic(major, P_OSREL_MAJOR(__FreeBSD_version), msg); - if (P_OSREL_MAJOR(__FreeBSD_version) >= major) - printf("Obsolete code will be removed soon: %s\n", msg); - else - printf("Deprecated code (to be removed in FreeBSD %d): %s\n", - major, msg); + va_list ap; + + gone_panic(major, P_OSREL_MAJOR(__FreeBSD_version), msg, ap); + va_start(ap, msg); + vprintf(msg, ap); + va_end(ap); + if (P_OSREL_MAJOR(__FreeBSD_version) < major) + printf("To be removed in FreeBSD %d\n", major); } void -_gone_in_dev(device_t dev, int major, const char *msg) +_gone_in_dev(device_t dev, int major, const char *msg, ...) { - gone_panic(major, P_OSREL_MAJOR(__FreeBSD_version), msg); - if (P_OSREL_MAJOR(__FreeBSD_version) >= major) - device_printf(dev, - "Obsolete code will be removed soon: %s\n", msg); - else + va_list ap; + + gone_panic(major, P_OSREL_MAJOR(__FreeBSD_version), msg, ap); + va_start(ap, msg); + device_printf(dev, msg, ap); + va_end(ap); + if (P_OSREL_MAJOR(__FreeBSD_version) < major) device_printf(dev, - "Deprecated code (to be removed in FreeBSD %d): %s\n", - major, msg); + "to be removed in FreeBSD %d\n", major); } #ifdef DDB DB_SHOW_COMMAND(device, db_show_device) { device_t dev; if (!have_addr) return; dev = (device_t)addr; db_printf("name: %s\n", device_get_nameunit(dev)); db_printf(" driver: %s\n", DRIVERNAME(dev->driver)); db_printf(" class: %s\n", DEVCLANAME(dev->devclass)); db_printf(" addr: %p\n", dev); db_printf(" parent: %p\n", dev->parent); db_printf(" softc: %p\n", dev->softc); db_printf(" ivars: %p\n", dev->ivars); } DB_SHOW_ALL_COMMAND(devices, db_show_all_devices) { device_t dev; TAILQ_FOREACH(dev, &bus_data_devices, devlink) { db_show_device((db_expr_t)dev, true, count, modif); } } #endif diff --git a/sys/sys/systm.h b/sys/sys/systm.h index 7cc02c77bea4..7a6edaef2cb8 100644 --- a/sys/sys/systm.h +++ b/sys/sys/systm.h @@ -1,596 +1,599 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1982, 1988, 1991, 1993 * The Regents of the University of California. All rights reserved. * (c) UNIX System Laboratories, Inc. * All or some portions of this file are derived from material licensed * to the University of California by American Telephone and Telegraph * Co. or Unix System Laboratories, Inc. and are reproduced herein with * the permission of UNIX System Laboratories, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef _SYS_SYSTM_H_ #define _SYS_SYSTM_H_ #include #include #include #include #include /* for people using printf mainly */ #include #include __NULLABILITY_PRAGMA_PUSH #ifdef _KERNEL extern int cold; /* nonzero if we are doing a cold boot */ extern int suspend_blocked; /* block suspend due to pending shutdown */ extern int rebooting; /* kern_reboot() has been called. */ extern const char version[]; /* system version */ extern const char compiler_version[]; /* compiler version */ extern const char copyright[]; /* system copyright */ extern int kstack_pages; /* number of kernel stack pages */ extern u_long pagesizes[]; /* supported page sizes */ extern long physmem; /* physical memory */ extern long realmem; /* 'real' memory */ extern char *rootdevnames[2]; /* names of possible root devices */ extern int boothowto; /* reboot flags, from console subsystem */ extern int bootverbose; /* nonzero to print verbose messages */ extern int maxusers; /* system tune hint */ extern int ngroups_max; /* max # of supplemental groups */ extern int vm_guest; /* Running as virtual machine guest? */ extern u_long maxphys; /* max raw I/O transfer size */ /* * Detected virtual machine guest types. The intention is to expand * and/or add to the VM_GUEST_VM type if specific VM functionality is * ever implemented (e.g. vendor-specific paravirtualization features). * Keep in sync with vm_guest_sysctl_names[]. */ enum VM_GUEST { VM_GUEST_NO = 0, VM_GUEST_VM, VM_GUEST_XEN, VM_GUEST_HV, VM_GUEST_VMWARE, VM_GUEST_KVM, VM_GUEST_BHYVE, VM_GUEST_VBOX, VM_GUEST_PARALLELS, VM_GUEST_NVMM, VM_GUEST_LAST }; #endif /* KERNEL */ /* * Align variables. */ #define __read_mostly __section(".data.read_mostly") #define __read_frequently __section(".data.read_frequently") #define __exclusive_cache_line __aligned(CACHE_LINE_SIZE) \ __section(".data.exclusive_cache_line") #if defined(_STANDALONE) struct ucred; #endif #ifdef _KERNEL #include /* MAXCPU */ #include /* curthread */ #include extern bool scheduler_stopped; /* * If we have already panic'd and this is the thread that called * panic(), then don't block on any mutexes but silently succeed. * Otherwise, the kernel will deadlock since the scheduler isn't * going to run the thread that holds any lock we need. */ #define SCHEDULER_STOPPED() __predict_false(scheduler_stopped) extern const int osreldate; extern const void *zero_region; /* address space maps to a zeroed page */ extern int unmapped_buf_allowed; #ifdef __LP64__ #define IOSIZE_MAX iosize_max() #define DEVFS_IOSIZE_MAX devfs_iosize_max() #else #define IOSIZE_MAX SSIZE_MAX #define DEVFS_IOSIZE_MAX SSIZE_MAX #endif /* * General function declarations. */ struct inpcb; struct lock_object; struct malloc_type; struct mtx; struct proc; struct socket; struct thread; struct tty; struct ucred; struct uio; struct _jmp_buf; struct trapframe; struct eventtimer; int setjmp(struct _jmp_buf *) __returns_twice; void longjmp(struct _jmp_buf *, int) __dead2; int dumpstatus(vm_offset_t addr, off_t count); int nullop(void); int eopnotsupp(void); int ureadc(int, struct uio *); void hashdestroy(void *, struct malloc_type *, u_long); void *hashinit(int count, struct malloc_type *type, u_long *hashmask); void *hashinit_flags(int count, struct malloc_type *type, u_long *hashmask, int flags); #define HASH_NOWAIT 0x00000001 #define HASH_WAITOK 0x00000002 void *phashinit(int count, struct malloc_type *type, u_long *nentries); void *phashinit_flags(int count, struct malloc_type *type, u_long *nentries, int flags); void cpu_flush_dcache(void *, size_t); void cpu_rootconf(void); void critical_enter_KBI(void); void critical_exit_KBI(void); void critical_exit_preempt(void); void init_param1(void); void init_param2(long physpages); void init_static_kenv(char *, size_t); void tablefull(const char *); /* * Allocate per-thread "current" state in the linuxkpi */ extern int (*lkpi_alloc_current)(struct thread *, int); int linux_alloc_current_noop(struct thread *, int); #if (defined(KLD_MODULE) && !defined(KLD_TIED)) || defined(KTR_CRITICAL) || !defined(_KERNEL) || defined(GENOFFSET) #define critical_enter() critical_enter_KBI() #define critical_exit() critical_exit_KBI() #else static __inline void critical_enter(void) { struct thread_lite *td; td = (struct thread_lite *)curthread; td->td_critnest++; atomic_interrupt_fence(); } static __inline void critical_exit(void) { struct thread_lite *td; td = (struct thread_lite *)curthread; KASSERT(td->td_critnest != 0, ("critical_exit: td_critnest == 0")); atomic_interrupt_fence(); td->td_critnest--; atomic_interrupt_fence(); if (__predict_false(td->td_owepreempt)) critical_exit_preempt(); } #endif #ifdef EARLY_PRINTF typedef void early_putc_t(int ch); extern early_putc_t *early_putc; #define CHECK_EARLY_PRINTF(x) \ __CONCAT(early_printf_, EARLY_PRINTF) == __CONCAT(early_printf_, x) #define early_printf_1 1 #define early_printf_mvebu 2 #define early_printf_ns8250 3 #define early_printf_pl011 4 #define early_printf_snps 5 #define early_printf_sbi 6 #else #define CHECK_EARLY_PRINTF(x) 0 #endif int kvprintf(char const *, void (*)(int, void*), void *, int, __va_list) __printflike(1, 0); void log(int, const char *, ...) __printflike(2, 3); void log_console(struct uio *); void vlog(int, const char *, __va_list) __printflike(2, 0); int asprintf(char **ret, struct malloc_type *mtp, const char *format, ...) __printflike(3, 4); int printf(const char *, ...) __printflike(1, 2); int snprintf(char *, size_t, const char *, ...) __printflike(3, 4); int sprintf(char *buf, const char *, ...) __printflike(2, 3); int uprintf(const char *, ...) __printflike(1, 2); int vprintf(const char *, __va_list) __printflike(1, 0); int vasprintf(char **ret, struct malloc_type *mtp, const char *format, __va_list ap) __printflike(3, 0); int vsnprintf(char *, size_t, const char *, __va_list) __printflike(3, 0); int vsnrprintf(char *, size_t, int, const char *, __va_list) __printflike(4, 0); int vsprintf(char *buf, const char *, __va_list) __printflike(2, 0); int sscanf(const char *, char const * _Nonnull, ...) __scanflike(2, 3); int vsscanf(const char * _Nonnull, char const * _Nonnull, __va_list) __scanflike(2, 0); long strtol(const char *, char **, int); u_long strtoul(const char *, char **, int); quad_t strtoq(const char *, char **, int); u_quad_t strtouq(const char *, char **, int); void tprintf(struct proc *p, int pri, const char *, ...) __printflike(3, 4); void vtprintf(struct proc *, int, const char *, __va_list) __printflike(3, 0); void hexdump(const void *ptr, int length, const char *hdr, int flags); #define HD_COLUMN_MASK 0xff #define HD_DELIM_MASK 0xff00 #define HD_OMIT_COUNT (1 << 16) #define HD_OMIT_HEX (1 << 17) #define HD_OMIT_CHARS (1 << 18) #define ovbcopy(f, t, l) bcopy((f), (t), (l)) void explicit_bzero(void * _Nonnull, size_t); void *memset(void * _Nonnull buf, int c, size_t len); void *memcpy(void * _Nonnull to, const void * _Nonnull from, size_t len); void *memmove(void * _Nonnull dest, const void * _Nonnull src, size_t n); int memcmp(const void *b1, const void *b2, size_t len); #ifdef SAN_NEEDS_INTERCEPTORS #define SAN_INTERCEPTOR(func) \ __CONCAT(SAN_INTERCEPTOR_PREFIX, __CONCAT(_, func)) void *SAN_INTERCEPTOR(memset)(void *, int, size_t); void *SAN_INTERCEPTOR(memcpy)(void *, const void *, size_t); void *SAN_INTERCEPTOR(memmove)(void *, const void *, size_t); int SAN_INTERCEPTOR(memcmp)(const void *, const void *, size_t); #ifndef SAN_RUNTIME #define bcopy(from, to, len) SAN_INTERCEPTOR(memmove)((to), (from), (len)) #define bzero(buf, len) SAN_INTERCEPTOR(memset)((buf), 0, (len)) #define bcmp(b1, b2, len) SAN_INTERCEPTOR(memcmp)((b1), (b2), (len)) #define memset(buf, c, len) SAN_INTERCEPTOR(memset)((buf), (c), (len)) #define memcpy(to, from, len) SAN_INTERCEPTOR(memcpy)((to), (from), (len)) #define memmove(dest, src, n) SAN_INTERCEPTOR(memmove)((dest), (src), (n)) #define memcmp(b1, b2, len) SAN_INTERCEPTOR(memcmp)((b1), (b2), (len)) #endif /* !SAN_RUNTIME */ #else /* !SAN_NEEDS_INTERCEPTORS */ #define bcopy(from, to, len) __builtin_memmove((to), (from), (len)) #define bzero(buf, len) __builtin_memset((buf), 0, (len)) #define bcmp(b1, b2, len) __builtin_memcmp((b1), (b2), (len)) #define memset(buf, c, len) __builtin_memset((buf), (c), (len)) #define memcpy(to, from, len) __builtin_memcpy((to), (from), (len)) #define memmove(dest, src, n) __builtin_memmove((dest), (src), (n)) #define memcmp(b1, b2, len) __builtin_memcmp((b1), (b2), (len)) #endif /* SAN_NEEDS_INTERCEPTORS */ void *memset_early(void * _Nonnull buf, int c, size_t len); #define bzero_early(buf, len) memset_early((buf), 0, (len)) void *memcpy_early(void * _Nonnull to, const void * _Nonnull from, size_t len); void *memmove_early(void * _Nonnull dest, const void * _Nonnull src, size_t n); #define bcopy_early(from, to, len) memmove_early((to), (from), (len)) #define copystr(src, dst, len, outlen) ({ \ size_t __r, __len, *__outlen; \ \ __len = (len); \ __outlen = (outlen); \ __r = strlcpy((dst), (src), __len); \ if (__outlen != NULL) \ *__outlen = ((__r >= __len) ? __len : __r + 1); \ ((__r >= __len) ? ENAMETOOLONG : 0); \ }) int __result_use_check copyinstr(const void * __restrict udaddr, void * _Nonnull __restrict kaddr, size_t len, size_t * __restrict lencopied); int __result_use_check copyin(const void * __restrict udaddr, void * _Nonnull __restrict kaddr, size_t len); int __result_use_check copyin_nofault(const void * __restrict udaddr, void * _Nonnull __restrict kaddr, size_t len); __nodiscard int copyout(const void * _Nonnull __restrict kaddr, void * __restrict udaddr, size_t len); __nodiscard int copyout_nofault( const void * _Nonnull __restrict kaddr, void * __restrict udaddr, size_t len); #ifdef SAN_NEEDS_INTERCEPTORS int SAN_INTERCEPTOR(copyin)(const void *, void *, size_t); int SAN_INTERCEPTOR(copyinstr)(const void *, void *, size_t, size_t *); int SAN_INTERCEPTOR(copyout)(const void *, void *, size_t); #ifndef SAN_RUNTIME #define copyin(u, k, l) SAN_INTERCEPTOR(copyin)((u), (k), (l)) #define copyinstr(u, k, l, lc) SAN_INTERCEPTOR(copyinstr)((u), (k), (l), (lc)) #define copyout(k, u, l) SAN_INTERCEPTOR(copyout)((k), (u), (l)) #endif /* !SAN_RUNTIME */ #endif /* SAN_NEEDS_INTERCEPTORS */ int fubyte(volatile const void *base); long fuword(volatile const void *base); int fuword16(volatile const void *base); int32_t fuword32(volatile const void *base); int64_t fuword64(volatile const void *base); int __result_use_check fueword(volatile const void *base, long *val); int __result_use_check fueword32(volatile const void *base, int32_t *val); int __result_use_check fueword64(volatile const void *base, int64_t *val); __nodiscard int subyte(volatile void *base, int byte); __nodiscard int suword(volatile void *base, long word); __nodiscard int suword16(volatile void *base, int word); __nodiscard int suword32(volatile void *base, int32_t word); __nodiscard int suword64(volatile void *base, int64_t word); uint32_t casuword32(volatile uint32_t *base, uint32_t oldval, uint32_t newval); u_long casuword(volatile u_long *p, u_long oldval, u_long newval); int casueword32(volatile uint32_t *base, uint32_t oldval, uint32_t *oldvalp, uint32_t newval); int casueword(volatile u_long *p, u_long oldval, u_long *oldvalp, u_long newval); #if defined(SAN_NEEDS_INTERCEPTORS) && !defined(KCSAN) int SAN_INTERCEPTOR(fubyte)(volatile const void *base); int SAN_INTERCEPTOR(fuword16)(volatile const void *base); int SAN_INTERCEPTOR(fueword)(volatile const void *base, long *val); int SAN_INTERCEPTOR(fueword32)(volatile const void *base, int32_t *val); int SAN_INTERCEPTOR(fueword64)(volatile const void *base, int64_t *val); int SAN_INTERCEPTOR(subyte)(volatile void *base, int byte); int SAN_INTERCEPTOR(suword)(volatile void *base, long word); int SAN_INTERCEPTOR(suword16)(volatile void *base, int word); int SAN_INTERCEPTOR(suword32)(volatile void *base, int32_t word); int SAN_INTERCEPTOR(suword64)(volatile void *base, int64_t word); int SAN_INTERCEPTOR(casueword32)(volatile uint32_t *base, uint32_t oldval, uint32_t *oldvalp, uint32_t newval); int SAN_INTERCEPTOR(casueword)(volatile u_long *p, u_long oldval, u_long *oldvalp, u_long newval); #ifndef SAN_RUNTIME #define fubyte(b) SAN_INTERCEPTOR(fubyte)((b)) #define fuword16(b) SAN_INTERCEPTOR(fuword16)((b)) #define fueword(b, v) SAN_INTERCEPTOR(fueword)((b), (v)) #define fueword32(b, v) SAN_INTERCEPTOR(fueword32)((b), (v)) #define fueword64(b, v) SAN_INTERCEPTOR(fueword64)((b), (v)) #define subyte(b, w) SAN_INTERCEPTOR(subyte)((b), (w)) #define suword(b, w) SAN_INTERCEPTOR(suword)((b), (w)) #define suword16(b, w) SAN_INTERCEPTOR(suword16)((b), (w)) #define suword32(b, w) SAN_INTERCEPTOR(suword32)((b), (w)) #define suword64(b, w) SAN_INTERCEPTOR(suword64)((b), (w)) #define casueword32(b, o, p, n) SAN_INTERCEPTOR(casueword32)((b), (o), (p), (n)) #define casueword(b, o, p, n) SAN_INTERCEPTOR(casueword)((b), (o), (p), (n)) #endif /* !SAN_RUNTIME */ #endif /* SAN_NEEDS_INTERCEPTORS && !KCSAN */ int sysbeep(int hertz, sbintime_t duration); void hardclock(int cnt, int usermode); void hardclock_sync(int cpu); void statclock(int cnt, int usermode); void profclock(int cnt, int usermode, uintfptr_t pc); int hardclockintr(void); void startprofclock(struct proc *); void stopprofclock(struct proc *); void cpu_startprofclock(void); void cpu_stopprofclock(void); void suspendclock(void); void resumeclock(void); sbintime_t cpu_idleclock(void); void cpu_activeclock(void); void cpu_new_callout(int cpu, sbintime_t bt, sbintime_t bt_opt); void cpu_et_frequency(struct eventtimer *et, uint64_t newfreq); extern int cpu_disable_c2_sleep; extern int cpu_disable_c3_sleep; extern void (*tcp_hpts_softclock)(void); extern volatile uint32_t __read_frequently hpts_that_need_softclock; #define tcp_hpts_softclock() do { \ if (hpts_that_need_softclock > 0) \ tcp_hpts_softclock(); \ } while (0) char *kern_getenv(const char *name); void freeenv(char *env); int getenv_int(const char *name, int *data); int getenv_uint(const char *name, unsigned int *data); int getenv_long(const char *name, long *data); int getenv_ulong(const char *name, unsigned long *data); int getenv_string(const char *name, char *data, int size); int getenv_int64(const char *name, int64_t *data); int getenv_uint64(const char *name, uint64_t *data); int getenv_quad(const char *name, quad_t *data); int getenv_bool(const char *name, bool *data); bool getenv_is_true(const char *name); bool getenv_is_false(const char *name); int kern_setenv(const char *name, const char *value); int kern_unsetenv(const char *name); int testenv(const char *name); int getenv_array(const char *name, void *data, int size, int *psize, int type_size, bool allow_signed); #define GETENV_UNSIGNED false /* negative numbers not allowed */ #define GETENV_SIGNED true /* negative numbers allowed */ typedef uint64_t (cpu_tick_f)(void); void set_cputicker(cpu_tick_f *func, uint64_t freq, bool isvariable); extern cpu_tick_f *cpu_ticks; uint64_t cpu_tickrate(void); uint64_t cputick2usec(uint64_t tick); #include /* Initialize the world */ void consinit(void); void cpu_initclocks(void); void cpu_initclocks_bsp(void); void cpu_initclocks_ap(void); void usrinfoinit(void); /* Finalize the world */ void kern_reboot(int) __dead2; void shutdown_nice(int); /* Stubs for obsolete functions that used to be for interrupt management */ static __inline intrmask_t splhigh(void) { return 0; } static __inline intrmask_t splimp(void) { return 0; } static __inline intrmask_t splnet(void) { return 0; } static __inline intrmask_t spltty(void) { return 0; } static __inline void splx(intrmask_t ipl __unused) { return; } /* * Common `proc' functions are declared here so that proc.h can be included * less often. */ int _sleep(const void * _Nonnull chan, struct lock_object *lock, int pri, const char *wmesg, sbintime_t sbt, sbintime_t pr, int flags); #define msleep(chan, mtx, pri, wmesg, timo) \ _sleep((chan), &(mtx)->lock_object, (pri), (wmesg), \ tick_sbt * (timo), 0, C_HARDCLOCK) #define msleep_sbt(chan, mtx, pri, wmesg, bt, pr, flags) \ _sleep((chan), &(mtx)->lock_object, (pri), (wmesg), (bt), (pr), \ (flags)) int msleep_spin_sbt(const void * _Nonnull chan, struct mtx *mtx, const char *wmesg, sbintime_t sbt, sbintime_t pr, int flags); #define msleep_spin(chan, mtx, wmesg, timo) \ msleep_spin_sbt((chan), (mtx), (wmesg), tick_sbt * (timo), \ 0, C_HARDCLOCK) int pause_sbt(const char *wmesg, sbintime_t sbt, sbintime_t pr, int flags); static __inline int pause(const char *wmesg, int timo) { return (pause_sbt(wmesg, tick_sbt * timo, 0, C_HARDCLOCK)); } #define pause_sig(wmesg, timo) \ pause_sbt((wmesg), tick_sbt * (timo), 0, C_HARDCLOCK | C_CATCH) #define tsleep(chan, pri, wmesg, timo) \ _sleep((chan), NULL, (pri), (wmesg), tick_sbt * (timo), \ 0, C_HARDCLOCK) #define tsleep_sbt(chan, pri, wmesg, bt, pr, flags) \ _sleep((chan), NULL, (pri), (wmesg), (bt), (pr), (flags)) void wakeup(const void *chan); void wakeup_one(const void *chan); void wakeup_any(const void *chan); /* * Common `struct cdev *' stuff are declared here to avoid #include poisoning */ struct cdev; dev_t dev2udev(struct cdev *x); const char *devtoname(struct cdev *cdev); #ifdef __LP64__ size_t devfs_iosize_max(void); size_t iosize_max(void); #endif int poll_no_poll(int events); /* XXX: Should be void nanodelay(u_int nsec); */ void DELAY(int usec); int kcmp_cmp(uintptr_t a, uintptr_t b); /* Root mount holdback API */ struct root_hold_token { int flags; const char *who; TAILQ_ENTRY(root_hold_token) list; }; struct root_hold_token *root_mount_hold(const char *identifier); void root_mount_hold_token(const char *identifier, struct root_hold_token *h); void root_mount_rel(struct root_hold_token *h); int root_mounted(void); /* * Unit number allocation API. (kern/subr_unit.c) */ struct unrhdr; #define UNR_NO_MTX ((void *)(uintptr_t)-1) struct unrhdr *new_unrhdr(int low, int high, struct mtx *mutex); void init_unrhdr(struct unrhdr *uh, int low, int high, struct mtx *mutex); void delete_unrhdr(struct unrhdr *uh); void clear_unrhdr(struct unrhdr *uh); void clean_unrhdr(struct unrhdr *uh); void clean_unrhdrl(struct unrhdr *uh); int alloc_unr(struct unrhdr *uh); int alloc_unr_specific(struct unrhdr *uh, u_int item); int alloc_unrl(struct unrhdr *uh); void free_unr(struct unrhdr *uh, u_int item); void *create_iter_unr(struct unrhdr *uh); int next_iter_unr(void *handle); void free_iter_unr(void *handle); struct unrhdr64 { uint64_t counter; }; static __inline void new_unrhdr64(struct unrhdr64 *unr64, uint64_t low) { unr64->counter = low; } static __inline uint64_t alloc_unr64(struct unrhdr64 *unr64) { return (atomic_fetchadd_64(&unr64->counter, 1)); } void intr_prof_stack_use(struct thread *td, struct trapframe *frame); void counted_warning(unsigned *counter, const char *msg); /* * APIs to manage deprecation and obsolescence. */ -void _gone_in(int major, const char *msg); -void _gone_in_dev(device_t dev, int major, const char *msg); +void _gone_in(int major, const char *msg, ...) __printflike(2, 3); +void _gone_in_dev(device_t dev, int major, const char *msg, ...) + __printflike(3, 4); #ifdef NO_OBSOLETE_CODE #define __gone_ok(m, msg) \ _Static_assert(m < P_OSREL_MAJOR(__FreeBSD_version)), \ - "Obsolete code: " msg); + "Obsolete code: " msg) #else #define __gone_ok(m, msg) #endif -#define gone_in(major, msg) __gone_ok(major, msg) _gone_in(major, msg) -#define gone_in_dev(dev, major, msg) __gone_ok(major, msg) _gone_in_dev(dev, major, msg) +#define gone_in(major, msg, ...) __gone_ok(major, msg) \ + _gone_in(major, msg __VA_OPT__(,) __VA_ARGS__) +#define gone_in_dev(dev, major, msg, ...) __gone_ok(major, msg) \ + _gone_in_dev(dev, major, msg __VA_OPT__(,) __VA_ARGS__) #ifdef INVARIANTS #define __diagused #else #define __diagused __unused #endif #ifdef WITNESS #define __witness_used #else #define __witness_used __unused #endif #endif /* _KERNEL */ __NULLABILITY_PRAGMA_POP #endif /* !_SYS_SYSTM_H_ */