#include <bits/stdc++.h>

using namespace std;

template<typename S, typename T>
ostream& operator<<(ostream& out, const pair<S, T> p) {
    out << "(" << p.first << "," << p.second << ")";
    return out;
}

template<typename T>
ostream& operator<<(ostream& out, const vector<T>& v) {
    for (auto a: v)
        out << a << " ";
    return out;
}

template<typename T>
ostream& operator<<(ostream& out, const set<T>& S) {
    for (auto a: S)
        cout << a << " ";
    return out;
}

template<typename T>
ostream& operator<<(ostream& out, const multiset<T>& S) {
    for (auto a: S)
        cout << a << " ";
    return out;
}

template<typename S, typename T>
ostream& operator<<(ostream& out, const map<S, T>& M) {
    for (auto m: M)
        cout << "(" << m.first << "->" << m.second << ") ";
    return out;
}

template<typename S, typename T>
pair<S, T> operator+(pair<S, T> a, pair<S, T> b) {
    return make_pair(a.first + b.first, a.second + b.second);
}

template<typename S, typename T>
pair<S, T> operator-(pair<S, T> a, pair<S, T> b) {
    return make_pair(a.first - b.first, a.second - b.second);
}

template<typename T>
struct GridGraph {
    int n;
    int m;
    vector<vector<T>> W;
    vector<vector<int>> vis;
    vector<vector<int>> dist;
    vector<pair<int,int>> topo_sort;
    vector<vector<int>> dp;
    vector<pair<int, int>> movs;
    vector<pair<int, int>> movs4 = {{0,1},{-1,0},{0,-1},{1,0}};
    vector<pair<int, int>> movs8 = {{0,1},{-1,1},{-1,0},{-1,-1},{0,-1},{1,-1},{1,0},{1,1}};
    vector<pair<int, int>> movs9 = {{-1,2},{-2,1},{-2,-1},{-1,-2},{1,-2},{2,-1},{2,1},{1,2}};

    void init(int n, int m) {
        this->n = n;
        this->m = m;
        // FIXME
        movs = movs4;
    }

    void init(vector<vector<T>>& A) {
        int n = A.size();
        int m = A[0].size();
        init(n, m);
        W = A;
    }

    void init(vector<string>& A) {
        int n = A.size();
        int m = A[0].size();
        init(n, m);
        W.resize(n);
        for (int i = 0; i < n; i++) {
            W[i].resize(m);
            for (int j = 0; j < m; j++) {
                W[i][j] = A[i][j];
            }
        }
    }

    bool is_edge_allowed(pair<int,int> s, pair<int,int> t) {
        // FIXME
        // return W[s.first][s.second] + 1 == W[t.first][t.second];
        return true;
    }

    bool is_node_allowed(pair<int,int> s) {
        // FIXME
        T val = W[s.first][s.second];
        // return val == '.';
        return val == '.';
    }

    void dfs(pair<int, int> s) {
        int x = s.first;
        int y = s.second;
        vis[x][y] = 1;
        for (auto mov: movs) {
            auto t = s + mov;
            int nx = t.first;
            int ny = t.second;
            if (nx >= 0 && nx < n && ny >= 0 && ny < m) {
                if (is_node_allowed(t)) {
                    if (is_edge_allowed(s, t)) {
                        if (vis[nx][ny] == 0) {
                            dfs(t);
                        }
                    }
                }
            }
        }
        vis[x][y] = 2;
        topo_sort.push_back(s);
    }

    void source_dfs(vector<pair<int,int>>& A) {
        vis.resize(n);
        for (int i = 0; i < n; i++)
            vis[i].assign(m, 0);
        topo_sort.clear();
        for (auto s: A) {
            int x = s.first;
            int y = s.second;
            if (is_node_allowed(s)) {
                if (vis[x][y] == 0) {
                    dfs(s);
                }
            }
        }
        reverse(topo_sort.begin(), topo_sort.end());
    }

    void source_dfs(pair<int,int> s) {
        vector<pair<int,int>> A;
        A.push_back(s);
        source_dfs(A);
    }

    void source_dfs() {
        vector<pair<int,int>> A;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                A.emplace_back(i, j);
            }
        }
        source_dfs(A);
    }

    void source_dynamic_porgramming() {
        dp.resize(n);
        for (int i = 0; i < n; i++)
            dp[i].assign(m, 1);
        for (int i = topo_sort.size() - 1; i >= 0; i--) {
            auto s = topo_sort[i];
            int x = s.first;
            int y = s.second;
            for (auto mov: movs) {
                auto t = s + mov;
                int nx = t.first;
                int ny = t.second;
                if (nx >= 0 && nx < n && ny >= 0 && ny < m) {
                    if (is_node_allowed(t)) {
                        if (is_edge_allowed(s, t)) {
                            dp[x][y] = max(dp[x][y], dp[nx][ny] + 1);
                        }
                    }
                }
            }
        }
    }

    void source_bfs(vector<pair<int,int>>& src, pair<int,int> dst = {-1,-1}) {
        vis.resize(n);
        for (int i = 0; i < n; i++)
            vis[i].assign(m, 0);
        dist.resize(n);
        for (int i = 0; i < n; i++)
            dist[i].assign(m, 1e9);
        queue<pair<int,int>> q;
        for (auto s: src) {
            int x = s.first;
            int y = s.second;
            vis[x][y] = 1;
            dist[x][y] = 0;
            q.push(s);
        }
        while (!q.empty()) {
            auto s = q.front();
            q.pop();
            int x = s.first;
            int y = s.second;
            vis[x][y] = 2;
            if (s == dst)
                break;
            for (auto mov: movs) {
                auto t = s + mov;
                int nx = t.first;
                int ny = t.second;
                if (nx >= 0 && nx < n && ny >= 0 && ny < m) {
                    if (is_node_allowed(t)) {
                        if (is_edge_allowed(s, t)) {
                            if (vis[nx][ny] == 0) {
                                vis[nx][ny] = 1;
                                dist[nx][ny] = dist[x][y] + 1;
                                q.push({nx, ny});
                            }
                        }
                    }
                }
            }
        }
    }

    void source_bfs(pair<int,int> src, pair<int,int> dst = {1, -1}) {
        vector<pair<int,int>> A;
        A.push_back(src);
        source_bfs(A, dst);
    }

    void dump_grid() {
        for (int i = 0; i < n; i++)
            cout << "W[" << i << "]: " << W[i] << endl;
    }
    void dump() {
        for (int i = 0; i < vis.size(); i++)
            cout << "vis[" << i << "]; " << vis[i] << endl;
        for (int i = 0; i < dist.size(); i++)
            cout << "dist[" << i << "]: " << dist[i] << endl;
        for (int i = 0; i < dp.size(); i++)
            cout << "dp[" << i << "]: " << dp[i] << endl;
    }
};

GridGraph<char> graph;

int main() {
    ios_base::sync_with_stdio(0); cin.tie(0);
    int n, m;
    vector<string> A;
    cin >> n >> m;
    A.resize(n);
    for (int i = 0; i < n; i++)
        cin >> A[i];
    graph.init(A);
    //graph.dump_grid();
    graph.source_bfs({0, 0});
    //graph.dump();
    int max_val = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (graph.W[i][j] == '.') {
                graph.source_bfs({i, j});
                for (int x = 0; x < n; x++) {
                    for (int y = 0; y < m; y++) {
                        if (graph.vis[x][y] == 2) {
                            int val = graph.dist[x][y];
                            max_val = max(max_val, val);
                        }
                    }
                }
            }
        }
    }
    cout << max_val << endl;
    return 0;
}


Measured memory_bytes: 256