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,11 +1713,381 @@ CharDriverState *qemu_chr_open_pty(void) | ||
1713 | 1713 | ||
1714 | #endif /* !defined(_WIN32) */ | 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 | CharDriverState *qemu_chr_open(const char *filename) | 2088 | CharDriverState *qemu_chr_open(const char *filename) |
1717 | { | 2089 | { |
1718 | -#ifndef _WIN32 | ||
1719 | const char *p; | 2090 | const char *p; |
1720 | -#endif | ||
1721 | 2091 | ||
1722 | if (!strcmp(filename, "vc")) { | 2092 | if (!strcmp(filename, "vc")) { |
1723 | return text_console_init(&display_state); | 2093 | return text_console_init(&display_state); |
@@ -1743,11 +2113,28 @@ CharDriverState *qemu_chr_open(const char *filename) | @@ -1743,11 +2113,28 @@ CharDriverState *qemu_chr_open(const char *filename) | ||
1743 | return qemu_chr_open_tty(filename); | 2113 | return qemu_chr_open_tty(filename); |
1744 | } else | 2114 | } else |
1745 | #endif | 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 | return NULL; | 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 | /* network device redirectors */ | 2139 | /* network device redirectors */ |
1753 | 2140 | ||
@@ -3090,6 +3477,43 @@ int qemu_set_fd_handler(int fd, | @@ -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 | /* savevm/loadvm support */ | 3517 | /* savevm/loadvm support */ |
3094 | 3518 | ||
3095 | void qemu_put_buffer(QEMUFile *f, const uint8_t *buf, int size) | 3519 | void qemu_put_buffer(QEMUFile *f, const uint8_t *buf, int size) |
@@ -3955,12 +4379,18 @@ void main_loop_wait(int timeout) | @@ -3955,12 +4379,18 @@ void main_loop_wait(int timeout) | ||
3955 | fd_set rfds, wfds; | 4379 | fd_set rfds, wfds; |
3956 | int ret, nfds; | 4380 | int ret, nfds; |
3957 | struct timeval tv; | 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 | #ifdef _WIN32 | 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 | Sleep(timeout); | 4392 | Sleep(timeout); |
4393 | + } | ||
3964 | #endif | 4394 | #endif |
3965 | /* poll any events */ | 4395 | /* poll any events */ |
3966 | /* XXX: separate device handlers from system ones */ | 4396 | /* XXX: separate device handlers from system ones */ |
vl.h
@@ -204,6 +204,14 @@ int qemu_set_fd_handler(int fd, | @@ -204,6 +204,14 @@ int qemu_set_fd_handler(int fd, | ||
204 | IOHandler *fd_write, | 204 | IOHandler *fd_write, |
205 | void *opaque); | 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 | /* character device */ | 215 | /* character device */ |
208 | 216 | ||
209 | #define CHR_EVENT_BREAK 0 /* serial break char */ | 217 | #define CHR_EVENT_BREAK 0 /* serial break char */ |
@@ -237,6 +245,7 @@ typedef struct CharDriverState { | @@ -237,6 +245,7 @@ typedef struct CharDriverState { | ||
237 | int (*chr_ioctl)(struct CharDriverState *s, int cmd, void *arg); | 245 | int (*chr_ioctl)(struct CharDriverState *s, int cmd, void *arg); |
238 | IOEventHandler *chr_event; | 246 | IOEventHandler *chr_event; |
239 | void (*chr_send_event)(struct CharDriverState *chr, int event); | 247 | void (*chr_send_event)(struct CharDriverState *chr, int event); |
248 | + void (*chr_close)(struct CharDriverState *chr); | ||
240 | void *opaque; | 249 | void *opaque; |
241 | } CharDriverState; | 250 | } CharDriverState; |
242 | 251 |