Commit f331110f3539ee590b3a856d157f92fb34a88bce
1 parent
1236cab7
win32 serial port support (initial patch by kazu
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@1807 c046a42c-6fe2-441c-8c8c-71466251a162
Showing
2 changed files
with
444 additions
and
5 deletions
vl.c
| ... | ... | @@ -1713,11 +1713,381 @@ CharDriverState *qemu_chr_open_pty(void) |
| 1713 | 1713 | |
| 1714 | 1714 | #endif /* !defined(_WIN32) */ |
| 1715 | 1715 | |
| 1716 | +#ifdef _WIN32 | |
| 1717 | +typedef struct { | |
| 1718 | + IOCanRWHandler *fd_can_read; | |
| 1719 | + IOReadHandler *fd_read; | |
| 1720 | + void *win_opaque; | |
| 1721 | + int max_size; | |
| 1722 | + HANDLE hcom, hrecv, hsend; | |
| 1723 | + OVERLAPPED orecv, osend; | |
| 1724 | + BOOL fpipe; | |
| 1725 | + DWORD len; | |
| 1726 | +} WinCharState; | |
| 1727 | + | |
| 1728 | +#define NSENDBUF 2048 | |
| 1729 | +#define NRECVBUF 2048 | |
| 1730 | +#define MAXCONNECT 1 | |
| 1731 | +#define NTIMEOUT 5000 | |
| 1732 | + | |
| 1733 | +static int win_chr_poll(void *opaque); | |
| 1734 | +static int win_chr_pipe_poll(void *opaque); | |
| 1735 | + | |
| 1736 | +static void win_chr_close2(WinCharState *s) | |
| 1737 | +{ | |
| 1738 | + if (s->hsend) { | |
| 1739 | + CloseHandle(s->hsend); | |
| 1740 | + s->hsend = NULL; | |
| 1741 | + } | |
| 1742 | + if (s->hrecv) { | |
| 1743 | + CloseHandle(s->hrecv); | |
| 1744 | + s->hrecv = NULL; | |
| 1745 | + } | |
| 1746 | + if (s->hcom) { | |
| 1747 | + CloseHandle(s->hcom); | |
| 1748 | + s->hcom = NULL; | |
| 1749 | + } | |
| 1750 | + if (s->fpipe) | |
| 1751 | + qemu_del_polling_cb(win_chr_pipe_poll, s); | |
| 1752 | + else | |
| 1753 | + qemu_del_polling_cb(win_chr_poll, s); | |
| 1754 | +} | |
| 1755 | + | |
| 1756 | +static void win_chr_close(CharDriverState *chr) | |
| 1757 | +{ | |
| 1758 | + WinCharState *s = chr->opaque; | |
| 1759 | + win_chr_close2(s); | |
| 1760 | +} | |
| 1761 | + | |
| 1762 | +static int win_chr_init(WinCharState *s, const char *filename) | |
| 1763 | +{ | |
| 1764 | + COMMCONFIG comcfg; | |
| 1765 | + COMMTIMEOUTS cto = { 0, 0, 0, 0, 0}; | |
| 1766 | + COMSTAT comstat; | |
| 1767 | + DWORD size; | |
| 1768 | + DWORD err; | |
| 1769 | + | |
| 1770 | + s->hsend = CreateEvent(NULL, TRUE, FALSE, NULL); | |
| 1771 | + if (!s->hsend) { | |
| 1772 | + fprintf(stderr, "Failed CreateEvent\n"); | |
| 1773 | + goto fail; | |
| 1774 | + } | |
| 1775 | + s->hrecv = CreateEvent(NULL, TRUE, FALSE, NULL); | |
| 1776 | + if (!s->hrecv) { | |
| 1777 | + fprintf(stderr, "Failed CreateEvent\n"); | |
| 1778 | + goto fail; | |
| 1779 | + } | |
| 1780 | + | |
| 1781 | + s->hcom = CreateFile(filename, GENERIC_READ|GENERIC_WRITE, 0, NULL, | |
| 1782 | + OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0); | |
| 1783 | + if (s->hcom == INVALID_HANDLE_VALUE) { | |
| 1784 | + fprintf(stderr, "Failed CreateFile (%lu)\n", GetLastError()); | |
| 1785 | + s->hcom = NULL; | |
| 1786 | + goto fail; | |
| 1787 | + } | |
| 1788 | + | |
| 1789 | + if (!SetupComm(s->hcom, NRECVBUF, NSENDBUF)) { | |
| 1790 | + fprintf(stderr, "Failed SetupComm\n"); | |
| 1791 | + goto fail; | |
| 1792 | + } | |
| 1793 | + | |
| 1794 | + ZeroMemory(&comcfg, sizeof(COMMCONFIG)); | |
| 1795 | + size = sizeof(COMMCONFIG); | |
| 1796 | + GetDefaultCommConfig(filename, &comcfg, &size); | |
| 1797 | + comcfg.dcb.DCBlength = sizeof(DCB); | |
| 1798 | + CommConfigDialog(filename, NULL, &comcfg); | |
| 1799 | + | |
| 1800 | + if (!SetCommState(s->hcom, &comcfg.dcb)) { | |
| 1801 | + fprintf(stderr, "Failed SetCommState\n"); | |
| 1802 | + goto fail; | |
| 1803 | + } | |
| 1804 | + | |
| 1805 | + if (!SetCommMask(s->hcom, EV_ERR)) { | |
| 1806 | + fprintf(stderr, "Failed SetCommMask\n"); | |
| 1807 | + goto fail; | |
| 1808 | + } | |
| 1809 | + | |
| 1810 | + cto.ReadIntervalTimeout = MAXDWORD; | |
| 1811 | + if (!SetCommTimeouts(s->hcom, &cto)) { | |
| 1812 | + fprintf(stderr, "Failed SetCommTimeouts\n"); | |
| 1813 | + goto fail; | |
| 1814 | + } | |
| 1815 | + | |
| 1816 | + if (!ClearCommError(s->hcom, &err, &comstat)) { | |
| 1817 | + fprintf(stderr, "Failed ClearCommError\n"); | |
| 1818 | + goto fail; | |
| 1819 | + } | |
| 1820 | + qemu_add_polling_cb(win_chr_poll, s); | |
| 1821 | + return 0; | |
| 1822 | + | |
| 1823 | + fail: | |
| 1824 | + win_chr_close2(s); | |
| 1825 | + return -1; | |
| 1826 | +} | |
| 1827 | + | |
| 1828 | +static int win_chr_write(CharDriverState *chr, const uint8_t *buf, int len1) | |
| 1829 | +{ | |
| 1830 | + WinCharState *s = chr->opaque; | |
| 1831 | + DWORD len, ret, size, err; | |
| 1832 | + | |
| 1833 | + len = len1; | |
| 1834 | + ZeroMemory(&s->osend, sizeof(s->osend)); | |
| 1835 | + s->osend.hEvent = s->hsend; | |
| 1836 | + while (len > 0) { | |
| 1837 | + if (s->hsend) | |
| 1838 | + ret = WriteFile(s->hcom, buf, len, &size, &s->osend); | |
| 1839 | + else | |
| 1840 | + ret = WriteFile(s->hcom, buf, len, &size, NULL); | |
| 1841 | + if (!ret) { | |
| 1842 | + err = GetLastError(); | |
| 1843 | + if (err == ERROR_IO_PENDING) { | |
| 1844 | + ret = GetOverlappedResult(s->hcom, &s->osend, &size, TRUE); | |
| 1845 | + if (ret) { | |
| 1846 | + buf += size; | |
| 1847 | + len -= size; | |
| 1848 | + } else { | |
| 1849 | + break; | |
| 1850 | + } | |
| 1851 | + } else { | |
| 1852 | + break; | |
| 1853 | + } | |
| 1854 | + } else { | |
| 1855 | + buf += size; | |
| 1856 | + len -= size; | |
| 1857 | + } | |
| 1858 | + } | |
| 1859 | + return len1 - len; | |
| 1860 | +} | |
| 1861 | + | |
| 1862 | +static int win_chr_read_poll(WinCharState *s) | |
| 1863 | +{ | |
| 1864 | + s->max_size = s->fd_can_read(s->win_opaque); | |
| 1865 | + return s->max_size; | |
| 1866 | +} | |
| 1867 | + | |
| 1868 | +static void win_chr_readfile(WinCharState *s) | |
| 1869 | +{ | |
| 1870 | + int ret, err; | |
| 1871 | + uint8_t buf[1024]; | |
| 1872 | + DWORD size; | |
| 1873 | + | |
| 1874 | + ZeroMemory(&s->orecv, sizeof(s->orecv)); | |
| 1875 | + s->orecv.hEvent = s->hrecv; | |
| 1876 | + ret = ReadFile(s->hcom, buf, s->len, &size, &s->orecv); | |
| 1877 | + if (!ret) { | |
| 1878 | + err = GetLastError(); | |
| 1879 | + if (err == ERROR_IO_PENDING) { | |
| 1880 | + ret = GetOverlappedResult(s->hcom, &s->orecv, &size, TRUE); | |
| 1881 | + } | |
| 1882 | + } | |
| 1883 | + | |
| 1884 | + if (size > 0) { | |
| 1885 | + s->fd_read(s->win_opaque, buf, size); | |
| 1886 | + } | |
| 1887 | +} | |
| 1888 | + | |
| 1889 | +static void win_chr_read(WinCharState *s) | |
| 1890 | +{ | |
| 1891 | + if (s->len > s->max_size) | |
| 1892 | + s->len = s->max_size; | |
| 1893 | + if (s->len == 0) | |
| 1894 | + return; | |
| 1895 | + | |
| 1896 | + win_chr_readfile(s); | |
| 1897 | +} | |
| 1898 | + | |
| 1899 | +static int win_chr_poll(void *opaque) | |
| 1900 | +{ | |
| 1901 | + WinCharState *s = opaque; | |
| 1902 | + COMSTAT status; | |
| 1903 | + DWORD comerr; | |
| 1904 | + | |
| 1905 | + ClearCommError(s->hcom, &comerr, &status); | |
| 1906 | + if (status.cbInQue > 0) { | |
| 1907 | + s->len = status.cbInQue; | |
| 1908 | + win_chr_read_poll(s); | |
| 1909 | + win_chr_read(s); | |
| 1910 | + return 1; | |
| 1911 | + } | |
| 1912 | + return 0; | |
| 1913 | +} | |
| 1914 | + | |
| 1915 | +static void win_chr_add_read_handler(CharDriverState *chr, | |
| 1916 | + IOCanRWHandler *fd_can_read, | |
| 1917 | + IOReadHandler *fd_read, void *opaque) | |
| 1918 | +{ | |
| 1919 | + WinCharState *s = chr->opaque; | |
| 1920 | + | |
| 1921 | + s->fd_can_read = fd_can_read; | |
| 1922 | + s->fd_read = fd_read; | |
| 1923 | + s->win_opaque = opaque; | |
| 1924 | +} | |
| 1925 | + | |
| 1926 | +CharDriverState *qemu_chr_open_win(const char *filename) | |
| 1927 | +{ | |
| 1928 | + CharDriverState *chr; | |
| 1929 | + WinCharState *s; | |
| 1930 | + | |
| 1931 | + chr = qemu_mallocz(sizeof(CharDriverState)); | |
| 1932 | + if (!chr) | |
| 1933 | + return NULL; | |
| 1934 | + s = qemu_mallocz(sizeof(WinCharState)); | |
| 1935 | + if (!s) { | |
| 1936 | + free(chr); | |
| 1937 | + return NULL; | |
| 1938 | + } | |
| 1939 | + chr->opaque = s; | |
| 1940 | + chr->chr_write = win_chr_write; | |
| 1941 | + chr->chr_add_read_handler = win_chr_add_read_handler; | |
| 1942 | + chr->chr_close = win_chr_close; | |
| 1943 | + | |
| 1944 | + if (win_chr_init(s, filename) < 0) { | |
| 1945 | + free(s); | |
| 1946 | + free(chr); | |
| 1947 | + return NULL; | |
| 1948 | + } | |
| 1949 | + return chr; | |
| 1950 | +} | |
| 1951 | + | |
| 1952 | +static int win_chr_pipe_poll(void *opaque) | |
| 1953 | +{ | |
| 1954 | + WinCharState *s = opaque; | |
| 1955 | + DWORD size; | |
| 1956 | + | |
| 1957 | + PeekNamedPipe(s->hcom, NULL, 0, NULL, &size, NULL); | |
| 1958 | + if (size > 0) { | |
| 1959 | + s->len = size; | |
| 1960 | + win_chr_read_poll(s); | |
| 1961 | + win_chr_read(s); | |
| 1962 | + return 1; | |
| 1963 | + } | |
| 1964 | + return 0; | |
| 1965 | +} | |
| 1966 | + | |
| 1967 | +static int win_chr_pipe_init(WinCharState *s, const char *filename) | |
| 1968 | +{ | |
| 1969 | + OVERLAPPED ov; | |
| 1970 | + int ret; | |
| 1971 | + DWORD size; | |
| 1972 | + char openname[256]; | |
| 1973 | + | |
| 1974 | + s->fpipe = TRUE; | |
| 1975 | + | |
| 1976 | + s->hsend = CreateEvent(NULL, TRUE, FALSE, NULL); | |
| 1977 | + if (!s->hsend) { | |
| 1978 | + fprintf(stderr, "Failed CreateEvent\n"); | |
| 1979 | + goto fail; | |
| 1980 | + } | |
| 1981 | + s->hrecv = CreateEvent(NULL, TRUE, FALSE, NULL); | |
| 1982 | + if (!s->hrecv) { | |
| 1983 | + fprintf(stderr, "Failed CreateEvent\n"); | |
| 1984 | + goto fail; | |
| 1985 | + } | |
| 1986 | + | |
| 1987 | + snprintf(openname, sizeof(openname), "\\\\.\\pipe\\%s", filename); | |
| 1988 | + s->hcom = CreateNamedPipe(openname, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, | |
| 1989 | + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | | |
| 1990 | + PIPE_WAIT, | |
| 1991 | + MAXCONNECT, NSENDBUF, NRECVBUF, NTIMEOUT, NULL); | |
| 1992 | + if (s->hcom == INVALID_HANDLE_VALUE) { | |
| 1993 | + fprintf(stderr, "Failed CreateNamedPipe (%lu)\n", GetLastError()); | |
| 1994 | + s->hcom = NULL; | |
| 1995 | + goto fail; | |
| 1996 | + } | |
| 1997 | + | |
| 1998 | + ZeroMemory(&ov, sizeof(ov)); | |
| 1999 | + ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); | |
| 2000 | + ret = ConnectNamedPipe(s->hcom, &ov); | |
| 2001 | + if (ret) { | |
| 2002 | + fprintf(stderr, "Failed ConnectNamedPipe\n"); | |
| 2003 | + goto fail; | |
| 2004 | + } | |
| 2005 | + | |
| 2006 | + ret = GetOverlappedResult(s->hcom, &ov, &size, TRUE); | |
| 2007 | + if (!ret) { | |
| 2008 | + fprintf(stderr, "Failed GetOverlappedResult\n"); | |
| 2009 | + if (ov.hEvent) { | |
| 2010 | + CloseHandle(ov.hEvent); | |
| 2011 | + ov.hEvent = NULL; | |
| 2012 | + } | |
| 2013 | + goto fail; | |
| 2014 | + } | |
| 2015 | + | |
| 2016 | + if (ov.hEvent) { | |
| 2017 | + CloseHandle(ov.hEvent); | |
| 2018 | + ov.hEvent = NULL; | |
| 2019 | + } | |
| 2020 | + qemu_add_polling_cb(win_chr_pipe_poll, s); | |
| 2021 | + return 0; | |
| 2022 | + | |
| 2023 | + fail: | |
| 2024 | + win_chr_close2(s); | |
| 2025 | + return -1; | |
| 2026 | +} | |
| 2027 | + | |
| 2028 | + | |
| 2029 | +CharDriverState *qemu_chr_open_win_pipe(const char *filename) | |
| 2030 | +{ | |
| 2031 | + CharDriverState *chr; | |
| 2032 | + WinCharState *s; | |
| 2033 | + | |
| 2034 | + chr = qemu_mallocz(sizeof(CharDriverState)); | |
| 2035 | + if (!chr) | |
| 2036 | + return NULL; | |
| 2037 | + s = qemu_mallocz(sizeof(WinCharState)); | |
| 2038 | + if (!s) { | |
| 2039 | + free(chr); | |
| 2040 | + return NULL; | |
| 2041 | + } | |
| 2042 | + chr->opaque = s; | |
| 2043 | + chr->chr_write = win_chr_write; | |
| 2044 | + chr->chr_add_read_handler = win_chr_add_read_handler; | |
| 2045 | + chr->chr_close = win_chr_close; | |
| 2046 | + | |
| 2047 | + if (win_chr_pipe_init(s, filename) < 0) { | |
| 2048 | + free(s); | |
| 2049 | + free(chr); | |
| 2050 | + return NULL; | |
| 2051 | + } | |
| 2052 | + return chr; | |
| 2053 | +} | |
| 2054 | + | |
| 2055 | +CharDriverState *qemu_chr_open_win_file(HANDLE fd_out) | |
| 2056 | +{ | |
| 2057 | + CharDriverState *chr; | |
| 2058 | + WinCharState *s; | |
| 2059 | + | |
| 2060 | + chr = qemu_mallocz(sizeof(CharDriverState)); | |
| 2061 | + if (!chr) | |
| 2062 | + return NULL; | |
| 2063 | + s = qemu_mallocz(sizeof(WinCharState)); | |
| 2064 | + if (!s) { | |
| 2065 | + free(chr); | |
| 2066 | + return NULL; | |
| 2067 | + } | |
| 2068 | + s->hcom = fd_out; | |
| 2069 | + chr->opaque = s; | |
| 2070 | + chr->chr_write = win_chr_write; | |
| 2071 | + chr->chr_add_read_handler = win_chr_add_read_handler; | |
| 2072 | + return chr; | |
| 2073 | +} | |
| 2074 | + | |
| 2075 | +CharDriverState *qemu_chr_open_win_file_out(const char *file_out) | |
| 2076 | +{ | |
| 2077 | + HANDLE fd_out; | |
| 2078 | + | |
| 2079 | + fd_out = CreateFile(file_out, GENERIC_WRITE, FILE_SHARE_READ, NULL, | |
| 2080 | + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); | |
| 2081 | + if (fd_out == INVALID_HANDLE_VALUE) | |
| 2082 | + return NULL; | |
| 2083 | + | |
| 2084 | + return qemu_chr_open_win_file(fd_out); | |
| 2085 | +} | |
| 2086 | +#endif | |
| 2087 | + | |
| 1716 | 2088 | CharDriverState *qemu_chr_open(const char *filename) |
| 1717 | 2089 | { |
| 1718 | -#ifndef _WIN32 | |
| 1719 | 2090 | const char *p; |
| 1720 | -#endif | |
| 1721 | 2091 | |
| 1722 | 2092 | if (!strcmp(filename, "vc")) { |
| 1723 | 2093 | return text_console_init(&display_state); |
| ... | ... | @@ -1743,11 +2113,28 @@ CharDriverState *qemu_chr_open(const char *filename) |
| 1743 | 2113 | return qemu_chr_open_tty(filename); |
| 1744 | 2114 | } else |
| 1745 | 2115 | #endif |
| 2116 | +#ifdef _WIN32 | |
| 2117 | + if (strstart(filename, "COM", NULL)) { | |
| 2118 | + return qemu_chr_open_win(filename); | |
| 2119 | + } else | |
| 2120 | + if (strstart(filename, "pipe:", &p)) { | |
| 2121 | + return qemu_chr_open_win_pipe(p); | |
| 2122 | + } else | |
| 2123 | + if (strstart(filename, "file:", &p)) { | |
| 2124 | + return qemu_chr_open_win_file_out(p); | |
| 2125 | + } | |
| 2126 | +#endif | |
| 1746 | 2127 | { |
| 1747 | 2128 | return NULL; |
| 1748 | 2129 | } |
| 1749 | 2130 | } |
| 1750 | 2131 | |
| 2132 | +void qemu_chr_close(CharDriverState *chr) | |
| 2133 | +{ | |
| 2134 | + if (chr->chr_close) | |
| 2135 | + chr->chr_close(chr); | |
| 2136 | +} | |
| 2137 | + | |
| 1751 | 2138 | /***********************************************************/ |
| 1752 | 2139 | /* network device redirectors */ |
| 1753 | 2140 | |
| ... | ... | @@ -3090,6 +3477,43 @@ int qemu_set_fd_handler(int fd, |
| 3090 | 3477 | } |
| 3091 | 3478 | |
| 3092 | 3479 | /***********************************************************/ |
| 3480 | +/* Polling handling */ | |
| 3481 | + | |
| 3482 | +typedef struct PollingEntry { | |
| 3483 | + PollingFunc *func; | |
| 3484 | + void *opaque; | |
| 3485 | + struct PollingEntry *next; | |
| 3486 | +} PollingEntry; | |
| 3487 | + | |
| 3488 | +static PollingEntry *first_polling_entry; | |
| 3489 | + | |
| 3490 | +int qemu_add_polling_cb(PollingFunc *func, void *opaque) | |
| 3491 | +{ | |
| 3492 | + PollingEntry **ppe, *pe; | |
| 3493 | + pe = qemu_mallocz(sizeof(PollingEntry)); | |
| 3494 | + if (!pe) | |
| 3495 | + return -1; | |
| 3496 | + pe->func = func; | |
| 3497 | + pe->opaque = opaque; | |
| 3498 | + for(ppe = &first_polling_entry; *ppe != NULL; ppe = &(*ppe)->next); | |
| 3499 | + *ppe = pe; | |
| 3500 | + return 0; | |
| 3501 | +} | |
| 3502 | + | |
| 3503 | +void qemu_del_polling_cb(PollingFunc *func, void *opaque) | |
| 3504 | +{ | |
| 3505 | + PollingEntry **ppe, *pe; | |
| 3506 | + for(ppe = &first_polling_entry; *ppe != NULL; ppe = &(*ppe)->next) { | |
| 3507 | + pe = *ppe; | |
| 3508 | + if (pe->func == func && pe->opaque == opaque) { | |
| 3509 | + *ppe = pe->next; | |
| 3510 | + qemu_free(pe); | |
| 3511 | + break; | |
| 3512 | + } | |
| 3513 | + } | |
| 3514 | +} | |
| 3515 | + | |
| 3516 | +/***********************************************************/ | |
| 3093 | 3517 | /* savevm/loadvm support */ |
| 3094 | 3518 | |
| 3095 | 3519 | void qemu_put_buffer(QEMUFile *f, const uint8_t *buf, int size) |
| ... | ... | @@ -3955,12 +4379,18 @@ void main_loop_wait(int timeout) |
| 3955 | 4379 | fd_set rfds, wfds; |
| 3956 | 4380 | int ret, nfds; |
| 3957 | 4381 | struct timeval tv; |
| 4382 | + PollingEntry *pe; | |
| 4383 | + | |
| 3958 | 4384 | |
| 4385 | + /* XXX: need to suppress polling by better using win32 events */ | |
| 4386 | + ret = 0; | |
| 4387 | + for(pe = first_polling_entry; pe != NULL; pe = pe->next) { | |
| 4388 | + ret |= pe->func(pe->opaque); | |
| 4389 | + } | |
| 3959 | 4390 | #ifdef _WIN32 |
| 3960 | - /* XXX: see how to merge it with the select. The constraint is | |
| 3961 | - that the select must be interrupted by the timer */ | |
| 3962 | - if (timeout > 0) | |
| 4391 | + if (ret == 0 && timeout > 0) { | |
| 3963 | 4392 | Sleep(timeout); |
| 4393 | + } | |
| 3964 | 4394 | #endif |
| 3965 | 4395 | /* poll any events */ |
| 3966 | 4396 | /* XXX: separate device handlers from system ones */ | ... | ... |
vl.h
| ... | ... | @@ -204,6 +204,14 @@ int qemu_set_fd_handler(int fd, |
| 204 | 204 | IOHandler *fd_write, |
| 205 | 205 | void *opaque); |
| 206 | 206 | |
| 207 | +/* Polling handling */ | |
| 208 | + | |
| 209 | +/* return TRUE if no sleep should be done afterwards */ | |
| 210 | +typedef int PollingFunc(void *opaque); | |
| 211 | + | |
| 212 | +int qemu_add_polling_cb(PollingFunc *func, void *opaque); | |
| 213 | +void qemu_del_polling_cb(PollingFunc *func, void *opaque); | |
| 214 | + | |
| 207 | 215 | /* character device */ |
| 208 | 216 | |
| 209 | 217 | #define CHR_EVENT_BREAK 0 /* serial break char */ |
| ... | ... | @@ -237,6 +245,7 @@ typedef struct CharDriverState { |
| 237 | 245 | int (*chr_ioctl)(struct CharDriverState *s, int cmd, void *arg); |
| 238 | 246 | IOEventHandler *chr_event; |
| 239 | 247 | void (*chr_send_event)(struct CharDriverState *chr, int event); |
| 248 | + void (*chr_close)(struct CharDriverState *chr); | |
| 240 | 249 | void *opaque; |
| 241 | 250 | } CharDriverState; |
| 242 | 251 | ... | ... |