From 79cf6380eab75ecbd5faae2432eeedc93d4a3aad Mon Sep 17 00:00:00 2001 From: Daniel Kudrow Date: Wed, 12 Feb 2025 22:14:04 +0000 Subject: [PATCH] nvidia-oot: resurrect tegra_hv_comm TTY driver Port the tegra_hv_comm driver to the OOT kernel. This driver allows an IVC channel to be used as an emulated serial device. Make the following changes to simplify maintaining compatibility with different kernel versions: - Use a generic port type instead of defining a custom one. - Use existing conftest to determine whether platform_device.remove returns void. - Add a conftest to determine whether uart_state.xmit exists. - Use tty_port.xmit_fifo in kernels where uart_state.xmit has been removed. Jira HYP-20929 Change-Id: I1bc218f591d6d741fc39ae5392a9ea95bdc3f117 Signed-off-by: Daniel Kudrow Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3340420 (cherry picked from commit a67e7fd330be5d6c55e35c0ce810ff5124378ae7 in embedded/7.0.3.0) Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3301386 Reviewed-by: Bitan Biswas GVS: buildbot_gerritrpt --- BUILD.bazel | 1 + drivers/tty/serial/Makefile | 1 + drivers/tty/serial/tegra_hv_comm.c | 659 +++++++++++++++++++++++++++++ scripts/conftest/Makefile | 1 + scripts/conftest/conftest.sh | 16 + 5 files changed, 678 insertions(+) create mode 100644 drivers/tty/serial/tegra_hv_comm.c diff --git a/BUILD.bazel b/BUILD.bazel index 94ea57e8..86bff8ab 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -157,6 +157,7 @@ kernel_module( "drivers/spi/spi-tegra210-quad.ko", "drivers/thermal/max77851_thermal.ko", "drivers/tty/serial/wch_35x/wch.ko", + "drivers/tty/serial/tegra_hv_comm.ko", "drivers/usb/typec/fusb301.ko", "drivers/video/tegra/camera/tegra_camera_platform.ko", "drivers/video/tegra/dc/bridge/maxim_gmsl_dp_serializer.ko", diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index f472d69b..e0753121 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -1 +1,2 @@ obj-m += wch_35x/ +obj-m += tegra_hv_comm.o diff --git a/drivers/tty/serial/tegra_hv_comm.c b/drivers/tty/serial/tegra_hv_comm.c new file mode 100644 index 00000000..17c56476 --- /dev/null +++ b/drivers/tty/serial/tegra_hv_comm.c @@ -0,0 +1,659 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: Copyright (c) 2014-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +/* + * tegra_hv_comm.c: TTY over Tegra HV + * + * Very loosely based on altera_jtaguart.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#undef DEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DRV_NAME "tegra_hv_comm" + +/* The port type is not important to userspace so use PORT_GENERIC. */ +#ifndef PORT_GENERIC +#define PORT_GENERIC (-1) +#endif + +#include + +/* frame format is +* 0000: +* 0004: +* 0008: data +*/ + +#define HDR_SIZE 8 + +struct tegra_hv_comm { + struct uart_port port; + struct platform_device *pdev; + struct tegra_hv_ivc_cookie *ivck; + unsigned int sigs; + unsigned long imr; + unsigned int tx_en:1; + struct timer_list tx_timer; + unsigned int rx_en:1; + void *rx_frame; + u8 *rx_buf; /* always points to rx_frame + HDR_SIZE */ + void *tx_frame; + u8 *tx_buf; /* always points to tx_frame + HDR_SIZE */ + void *buf; + int rx_in, rx_cnt; +}; + +#define to_tegra_hv_comm(_x) \ + container_of(_x, struct tegra_hv_comm, port) + +static unsigned int tegra_hv_comm_tx_empty(struct uart_port *port) +{ + struct tegra_hv_comm *pp = to_tegra_hv_comm(port); + struct device *dev = &pp->pdev->dev; + + dev_dbg(dev, "%s\n", __func__); + + return tegra_hv_ivc_tx_empty(pp->ivck) ? TIOCSER_TEMT : 0; +} + +static unsigned int tegra_hv_comm_get_mctrl(struct uart_port *port) +{ + /* fake it */ + return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS; +} + +static void tegra_hv_comm_set_mctrl(struct uart_port *port, unsigned int sigs) +{ + struct tegra_hv_comm *pp = to_tegra_hv_comm(port); + struct device *dev = &pp->pdev->dev; + + /* nothing */ + dev_dbg(dev, "%s\n", __func__); +} + +static int xmit_active(struct tegra_hv_comm *pp) +{ + struct uart_port *port = &pp->port; +#ifdef NV_UART_STATE_HAS_XMIT /* Linux v6.10 */ + struct circ_buf *xmit = &port->state->xmit; + + return port->x_char != 0 || uart_circ_chars_pending(xmit) > 0; +#else + struct tty_port *tport = &port->state->port; + + return port->x_char != 0 || !kfifo_is_empty(&tport->xmit_fifo); +#endif +} + +static void xmit_timer_setup(struct tegra_hv_comm *pp, int in_timer) +{ + if (xmit_active(pp)) { + if (!pp->tx_en) { + pp->tx_en = 1; + mod_timer(&pp->tx_timer, jiffies + HZ / 8); + } + } else { + if (pp->tx_en) { + pp->tx_en = 0; + if (!in_timer) + del_timer(&pp->tx_timer); + } + } +} + +static void tegra_hv_comm_tx_chars(struct tegra_hv_comm *pp) +{ + struct uart_port *port = &pp->port; + struct device *dev = &pp->pdev->dev; +#ifdef NV_UART_STATE_HAS_XMIT + struct circ_buf *xmit = &port->state->xmit; +#else + struct tty_port *tport = &port->state->port; +#endif + unsigned int pending, count; + char *s; + int ret; + + dev_dbg(dev, "%s\n", __func__); + + if (!tegra_hv_ivc_can_write(pp->ivck)) + return; + + if (port->x_char) { + /* Send special char - probably flow control */ + + ((u32 *)pp->tx_frame)[0] = 1; /* count */ + ((u32 *)pp->tx_frame)[1] = 0; /* flags */ + s = pp->tx_frame + HDR_SIZE; + *s++ = port->x_char; + + count = (void *)s - pp->tx_frame + HDR_SIZE; + memset(pp->tx_frame + count, 0, pp->ivck->frame_size - count); + ret = tegra_hv_ivc_write(pp->ivck, pp->tx_frame, count); + if (ret != count) { + /* error */ + dev_err(dev, "%s: failed to write x_char\n", __func__); + return; + } + + port->x_char = 0; + port->icount.tx++; + return; + } + +#ifdef NV_UART_STATE_HAS_XMIT + pending = uart_circ_chars_pending(xmit); +#else + pending = kfifo_len(&tport->xmit_fifo); +#endif + if (pending > 0) { + count = pp->ivck->frame_size - HDR_SIZE; + if (count > pending) + count = pending; + if (count > 0) { + pending -= count; + + ((u32 *)pp->tx_frame)[0] = count; /* count */ + ((u32 *)pp->tx_frame)[1] = 0; /* flags */ + s = pp->tx_frame + HDR_SIZE; +#ifdef NV_UART_STATE_HAS_XMIT + while (count-- > 0) { + *s++ = xmit->buf[xmit->tail]; + xmit->tail = (xmit->tail + 1) & + (UART_XMIT_SIZE - 1); + port->icount.tx++; + } +#else + count = kfifo_out(&tport->xmit_fifo, s, count); + s += count; + port->icount.tx += count; +#endif + + /* count is now from start of frame */ + count = (void *)s - pp->tx_frame; + if (count > 0) + memset(pp->tx_frame + count, 0, + pp->ivck->frame_size - count); + + ret = tegra_hv_ivc_write(pp->ivck, pp->tx_frame, count); + if (ret != count) { + /* error */ + dev_err(dev, "%s: failed to write normal data\n", + __func__); + return; + } + + if (pending < WAKEUP_CHARS) + uart_write_wakeup(port); + } + } + +} + +static void tegra_hv_tx_timer_expired(struct timer_list *timer) +{ + struct tegra_hv_comm *pp = container_of(timer, struct tegra_hv_comm, tx_timer); + struct uart_port *port = &pp->port; + + spin_lock(&port->lock); + tegra_hv_comm_tx_chars(pp); + xmit_timer_setup(pp, 1); + spin_unlock(&port->lock); +} + +static void tegra_hv_comm_start_tx(struct uart_port *port) +{ + struct tegra_hv_comm *pp = to_tegra_hv_comm(port); + struct device *dev = &pp->pdev->dev; + + dev_dbg(dev, "%s\n", __func__); + + tegra_hv_comm_tx_chars(pp); + xmit_timer_setup(pp, 0); +} + +static void tegra_hv_comm_stop_tx(struct uart_port *port) +{ + struct tegra_hv_comm *pp = to_tegra_hv_comm(port); + struct device *dev = &pp->pdev->dev; + + dev_dbg(dev, "%s\n", __func__); + + if (pp->tx_en) { + pp->tx_en = 0; + del_timer_sync(&pp->tx_timer); + } +} + +static void tegra_hv_comm_stop_rx(struct uart_port *port) +{ + struct tegra_hv_comm *pp = to_tegra_hv_comm(port); + struct device *dev = &pp->pdev->dev; + + dev_dbg(dev, "%s\n", __func__); + + pp->rx_en = 0; +} + +static void tegra_hv_comm_break_ctl(struct uart_port *port, int break_state) +{ + /* nothing */ +} + +static void tegra_hv_comm_enable_ms(struct uart_port *port) +{ + /* nothing */ +} + +static void tegra_hv_comm_set_termios(struct uart_port *port, + struct ktermios *termios, + const struct ktermios *old) +{ + struct tegra_hv_comm *pp = to_tegra_hv_comm(port); + struct device *dev = &pp->pdev->dev; + + dev_dbg(dev, "%s\n", __func__); + + /* Just copy the old termios settings back */ + if (old) + tty_termios_copy_hw(termios, old); +} + +static void tegra_hv_comm_rx_chars(struct tegra_hv_comm *pp) +{ + struct uart_port *port = &pp->port; + struct device *dev = &pp->pdev->dev; + unsigned char ch, flag; + u32 cnt, flags; + int ret; + + dev_dbg(dev, "%s\n", __func__); + + for (;;) { + /* no more data, get more */ + while (pp->rx_in >= pp->rx_cnt && + tegra_hv_ivc_can_read(pp->ivck)) { + + ret = tegra_hv_ivc_read(pp->ivck, pp->rx_frame, + pp->ivck->frame_size); + + /* error */ + if (ret <= 0) { + dev_err(dev, "%s: ivc_read failed (ret=%d)\n", + __func__, ret); + break; + } + + /* not enough data */ + if (ret < HDR_SIZE) { + dev_err(dev, "%s: ivc_read short read (ret=%d)\n", + __func__, ret); + break; + } + + cnt = ((u32 *)pp->rx_frame)[0]; + flags = ((u32 *)pp->rx_frame)[1]; + + /* validate */ + if (cnt > pp->ivck->frame_size - HDR_SIZE) { + dev_err(dev, "%s: ivc_read bogus frame (cnt=%d)\n", + __func__, cnt); + break; + } + + pp->rx_cnt = cnt; + pp->rx_in = 0; + } + + if (pp->rx_in >= pp->rx_cnt) + break; + + /* next character */ + ch = pp->rx_buf[pp->rx_in++]; + + flag = TTY_NORMAL; + port->icount.rx++; + + if (uart_handle_sysrq_char(port, ch)) + continue; + uart_insert_char(port, 0, 0, ch, flag); + } + + tty_flip_buffer_push(&port->state->port); +} + +static irqreturn_t tegra_hv_comm_interrupt(int irq, void *data) +{ + struct uart_port *port = data; + struct tegra_hv_comm *pp = to_tegra_hv_comm(port); + struct device *dev = &pp->pdev->dev; + + dev_dbg(dev, "%s\n", __func__); + + spin_lock(&port->lock); + + /* until this function returns 0, the channel is unusable */ + if (tegra_hv_ivc_channel_notified(pp->ivck) == 0) { + if (pp->rx_en && tegra_hv_ivc_can_read(pp->ivck)) + tegra_hv_comm_rx_chars(pp); + + tegra_hv_comm_tx_chars(pp); + xmit_timer_setup(pp, 0); + } + + spin_unlock(&port->lock); + + return IRQ_HANDLED; +} + +static void tegra_hv_comm_config_port(struct uart_port *port, int flags) +{ + port->type = PORT_GENERIC; +} + +static int tegra_hv_comm_startup(struct uart_port *port) +{ + struct tegra_hv_comm *pp = to_tegra_hv_comm(port); + struct device *dev = &pp->pdev->dev; + unsigned long flags; + int ret, irq; + + dev_dbg(dev, "%s\n", __func__); + + irq = pp->ivck->irq; + ret = devm_request_irq(dev, irq, + tegra_hv_comm_interrupt, 0, + dev_name(dev), port); + if (ret) { + pr_err(DRV_NAME ": unable to request #%d irq=%d\n", + port->line, irq); + return ret; + } + + spin_lock_irqsave(&port->lock, flags); + pp->rx_en = 1; + if (tegra_hv_ivc_can_read(pp->ivck)) + tegra_hv_comm_rx_chars(pp); + spin_unlock_irqrestore(&port->lock, flags); + + return 0; +} + +static void tegra_hv_comm_shutdown(struct uart_port *port) +{ + struct tegra_hv_comm *pp = to_tegra_hv_comm(port); + struct device *dev = &pp->pdev->dev; + unsigned long flags; + int irq; + + dev_dbg(dev, "%s\n", __func__); + + irq = pp->ivck->irq; + + devm_free_irq(&pp->pdev->dev, irq, port); + + spin_lock_irqsave(&port->lock, flags); + pp->rx_en = 0; + if (pp->tx_en) { + pp->tx_en = 0; + del_timer(&pp->tx_timer); + } + spin_unlock_irqrestore(&port->lock, flags); +} + +static const char *tegra_hv_comm_type(struct uart_port *port) +{ + return (port->type == PORT_GENERIC) ? "Tegra HV" : NULL; +} + +static int tegra_hv_comm_request_port(struct uart_port *port) +{ + /* UARTs always present */ + return 0; +} + +static void tegra_hv_comm_release_port(struct uart_port *port) +{ + /* Nothing to release... */ +} + +static int tegra_hv_comm_verify_port(struct uart_port *port, + struct serial_struct *ser) +{ + if (ser->type != PORT_UNKNOWN && ser->type != PORT_GENERIC) + return -EINVAL; + return 0; +} + +/* + * Define the basic serial functions we support. + */ +static struct uart_ops tegra_hv_comm_ops = { + .tx_empty = tegra_hv_comm_tx_empty, + .get_mctrl = tegra_hv_comm_get_mctrl, + .set_mctrl = tegra_hv_comm_set_mctrl, + .start_tx = tegra_hv_comm_start_tx, + .stop_tx = tegra_hv_comm_stop_tx, + .stop_rx = tegra_hv_comm_stop_rx, + .enable_ms = tegra_hv_comm_enable_ms, + .break_ctl = tegra_hv_comm_break_ctl, + .startup = tegra_hv_comm_startup, + .shutdown = tegra_hv_comm_shutdown, + .set_termios = tegra_hv_comm_set_termios, + .type = tegra_hv_comm_type, + .request_port = tegra_hv_comm_request_port, + .release_port = tegra_hv_comm_release_port, + .config_port = tegra_hv_comm_config_port, + .verify_port = tegra_hv_comm_verify_port, +}; + +#define TEGRA_HV_COMM_MAJOR 204 +#define TEGRA_HV_COMM_MINOR 213 +#define TEGRA_HV_COMM_MAXPORTS 1 +static struct tegra_hv_comm tegra_hv_comm_ports[TEGRA_HV_COMM_MAXPORTS]; + +static struct uart_driver tegra_hv_comm_driver = { + .owner = THIS_MODULE, + .driver_name = "tegra_hv_comm", + .dev_name = "ttyTGRHVC", + .major = TEGRA_HV_COMM_MAJOR, + .minor = TEGRA_HV_COMM_MINOR, + .nr = TEGRA_HV_COMM_MAXPORTS, + .cons = NULL, /* no console supported yet */ +}; + +static int tegra_hv_comm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *dn, *hv_dn; + struct tegra_hv_comm *pp; + struct uart_port *port; + int i = pdev->id; + int ret; + u32 id; + + if (!is_tegra_hypervisor_mode()) { + dev_info(dev, "Hypervisor is not present\n"); + return -ENODEV; + } + + /* If the platform_device does not have an ID (i.e., no numerical suffix in + * its device node name), treat it as having one port. + */ + if (i < 0) + i = 0; + + if (i >= TEGRA_HV_COMM_MAXPORTS) + return -EINVAL; + + dn = dev->of_node; + if (dn == NULL) { + dev_err(dev, "No OF data\n"); + return -EINVAL; + } + + hv_dn = of_parse_phandle(dn, "ivc", 0); + if (hv_dn == NULL) { + dev_err(dev, "Failed to parse phandle of ivc prop\n"); + return -EINVAL; + } + + ret = of_property_read_u32_index(dn, "ivc", 1, &id); + if (ret != 0) { + dev_err(dev, "Failed to read IVC property ID\n"); + of_node_put(hv_dn); + return ret; + } + + /* zero out */ + pp = &tegra_hv_comm_ports[i]; + memset(pp, 0, sizeof(*pp)); + + port = &pp->port; + + pp->ivck = tegra_hv_ivc_reserve(hv_dn, id, NULL); + + of_node_put(hv_dn); + + if (IS_ERR_OR_NULL(pp->ivck)) { + dev_err(dev, "Failed to reserve IVC channel %d\n", id); + ret = PTR_ERR(pp->ivck); + pp->ivck = NULL; + return ret; + } + + /* make sure the frame size is sufficient */ + if (pp->ivck->frame_size <= HDR_SIZE) { + dev_err(dev, "frame size too small to support COMM\n"); + ret = -EINVAL; + goto out_unreserve; + } + + dev_info(dev, "Reserved IVC channel #%d - frame_size=%d\n", + id, pp->ivck->frame_size); + + /* allocate temporary frames */ + pp->buf = devm_kzalloc(dev, pp->ivck->frame_size * 2, GFP_KERNEL); + if (pp->buf == NULL) { + dev_err(dev, "Failed to allocate buffers\n"); + ret = -ENOMEM; + goto out_unreserve; + } + pp->rx_frame = pp->buf; + pp->rx_buf = pp->rx_frame + HDR_SIZE; + pp->tx_frame = pp->rx_frame + pp->ivck->frame_size; + pp->tx_buf = pp->tx_frame + HDR_SIZE; + + timer_setup(&pp->tx_timer, tegra_hv_tx_timer_expired, 0); + + pp->pdev = pdev; + platform_set_drvdata(pdev, pp); + + port->line = i; + port->type = PORT_GENERIC; + port->iotype = SERIAL_IO_MEM; + port->ops = &tegra_hv_comm_ops; + port->flags = UPF_BOOT_AUTOCONF; + + /* + * start the channel reset process asynchronously. until the reset + * process completes, any attempt to use the ivc channel will return + * an error (e.g., all transmits will fail.) + */ + tegra_hv_ivc_channel_reset(pp->ivck); + + ret = uart_add_one_port(&tegra_hv_comm_driver, port); + if (ret != 0) { + dev_err(dev, "uart_add_one_port failed\n"); + goto out_unreserve; + } + + dev_info(dev, "ready\n"); + + return 0; + +out_unreserve: + tegra_hv_ivc_unreserve(pp->ivck); + return ret; +} + +#if defined(NV_PLATFORM_DRIVER_STRUCT_REMOVE_RETURNS_VOID) /* Linux v6.11 */ +static void tegra_hv_comm_remove(struct platform_device *pdev) +#else +static int tegra_hv_comm_remove(struct platform_device *pdev) +#endif +{ + struct tegra_hv_comm *pp = platform_get_drvdata(pdev); + struct uart_port *port; + + port = &pp->port; + + tegra_hv_ivc_unreserve(pp->ivck); + + uart_remove_one_port(&tegra_hv_comm_driver, port); + +#if !defined(NV_PLATFORM_DRIVER_STRUCT_REMOVE_RETURNS_VOID) + return 0; +#endif +} + +#ifdef CONFIG_OF +static struct of_device_id tegra_hv_comm_match[] = { + { .compatible = "nvidia,tegra-hv-comm", }, + {}, +}; +MODULE_DEVICE_TABLE(of, tegra_hv_comm_match); +#endif /* CONFIG_OF */ + +static struct platform_driver tegra_hv_comm_platform_driver = { + .probe = tegra_hv_comm_probe, + .remove = tegra_hv_comm_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(tegra_hv_comm_match), + }, +}; + +static int tegra_hv_comm_init(void) +{ + int rc; + + rc = uart_register_driver(&tegra_hv_comm_driver); + if (rc) + return rc; + rc = platform_driver_register(&tegra_hv_comm_platform_driver); + if (rc) + uart_unregister_driver(&tegra_hv_comm_driver); + return rc; +} + +static void tegra_hv_comm_exit(void) +{ + platform_driver_unregister(&tegra_hv_comm_platform_driver); + uart_unregister_driver(&tegra_hv_comm_driver); +} + +module_init(tegra_hv_comm_init); +module_exit(tegra_hv_comm_exit); + +MODULE_AUTHOR("Pantelis Antoniou "); +MODULE_DESCRIPTION("TTY over Tegra Hypervisor IVC channel"); +MODULE_LICENSE("GPL"); diff --git a/scripts/conftest/Makefile b/scripts/conftest/Makefile index 5f7970d5..d4b83096 100644 --- a/scripts/conftest/Makefile +++ b/scripts/conftest/Makefile @@ -230,6 +230,7 @@ NV_CONFTEST_FUNCTION_COMPILE_TESTS += timer_delete NV_CONFTEST_FUNCTION_COMPILE_TESTS += tty_operations_struct_send_xchar_has_u8_arg NV_CONFTEST_FUNCTION_COMPILE_TESTS += tty_operations_struct_write_has_u8_ptr_arg NV_CONFTEST_FUNCTION_COMPILE_TESTS += tty_operations_struct_set_termios_has_const_ktermios_arg +NV_CONFTEST_FUNCTION_COMPILE_TESTS += uart_state_has_xmit NV_CONFTEST_FUNCTION_COMPILE_TESTS += ufs_hba_variant_ops_has_set_dma_mask NV_CONFTEST_FUNCTION_COMPILE_TESTS += ufs_hba_variant_ops_pwr_change_notify_has_const_arg NV_CONFTEST_FUNCTION_COMPILE_TESTS += ufs_hba_variant_ops_suspend_has_status_arg diff --git a/scripts/conftest/conftest.sh b/scripts/conftest/conftest.sh index 7ad4ce6c..ad5309e5 100755 --- a/scripts/conftest/conftest.sh +++ b/scripts/conftest/conftest.sh @@ -8810,6 +8810,22 @@ compile_test() { compile_check_conftest "$CODE" "NV_TTY_OPERATIONS_STRUCT_SET_TERMIOS_HAS_CONST_KTERMIOS_ARG" "" "types" ;; + uart_state_has_xmit) + # + # Determine if the struct uart_state has a xmit member. + # + # Commit 1788cf6 ("tty: serial: switch from circ_buf to kfifo") in + # Linux 6.10 removed this member. + # + CODE=" + #include + int conftest_uart_state_has_xmit(void) { + return offsetof(struct uart_state, xmit); + }" + + compile_check_conftest "$CODE" "NV_UART_STATE_HAS_XMIT" "" "types" + ;; + ufs_hba_variant_ops_has_set_dma_mask) # # Determine if the 'struct ufs_hba_variant_ops' has a 'set_dma_mask'